Deliveries
This section contains 1 subgroups.
Create Delivery
/external/deliveriesCreate a normal domestic delivery using pickup and delivery address objects. The endpoint accepts coordinates and address text in the request. On success, it reloads the created row using the same delivery_list_view-backed detail shape used by external get-delivery, then returns it under delivery.
Success response
201with{ success, error, message, currency_code, delivery }.deliveryis detail-shaped likeGET /external/deliveries/{id}.deliveryincludespickup_location,delivery_location,pickup_warehouse_id,dropoff_warehouse_id,pickup_warehouse,dropoff_warehouse,delivery_services,package_details,unit_size,cod_amount,notes, andspecial_instructionswhen available.delivery.idremainsdelivery_codefor external create compatibility.
Important errors
400validation failure,delivery_service_not_found,external_reference_id_already_exists,unit_size_or_dimensions_required, price/zone errors.500internal_server_error.
External delivery response enrichment
Top-level response includes
currency_code. Each delivery object also includescurrency_code.delivery.track_orderis the public tracking page URL built as{track_url}/tracking?code={delivery_code};track_urlresolves from tenanttrack_orders.track_url, then theWEB_URLsecret, then systemexternal_links.delivery_customer_portal_url, thenhttps://staging-app.ondi.io.delivery.customer_receipt_urlis a signed receipt URL when a successful delivery payment receipt exists; otherwisenull.delivery.status_updated_atis derived from the lateststatus_updates[].timestamp, falling back toupdated_at.delivery.delivery_servicesincludesdescriptionandimage_urlwhen available.delivery.proofsis grouped as{ pickup: [], delivery: [] }using OnDi proof records.delivery.proof_of_pickupanddelivery.proof_of_deliveryare aggregate proof summaries when captured, shaped as{ signature, photos }.delivery.failure_reasonis populated fromdelivery_failure_reasonwhen present.Top-level
delivery_typeis not returned; usedelivery.delivery_services.delivery_type.Response
payment_methodis documented aswallet | cash.
Headers
| Parameter | Type | Description |
|---|---|---|
X-API-Key | string | Example: |
Content-Type | string | Example: |
Body parameters
| Parameter | Type | Description |
|---|---|---|
pickup_addressRequired | object | Pickup address payload. |
pickup_address.latRequired | number | Pickup latitude. |
pickup_address.lngRequired | number | Pickup longitude. |
pickup_address.full_addressRequired | string | Full pickup address text. |
pickup_address.cityOptional | string | City name. |
pickup_address.stateOptional | string | State or province. |
pickup_address.countryOptional | string | Country name or code. |
pickup_address.postal_codeOptional | string | Postal or ZIP code. |
pickup_address.address_line1Optional | string | Address line 1. |
pickup_address.address_line2Optional | string | Address line 2. |
pickup_address.contact_infoOptional | object | Pickup contact details. |
pickup_address.contact_info.full_nameOptional | string | Contact full name. |
Show 40 more body fields
Additional body parameters
| Parameter | Type | Description |
|---|---|---|
pickup_address.contact_info.phoneOptional | string | Contact phone number. |
pickup_address.contact_info.emailOptional | string | Contact email address. |
delivery_addressRequired | object | Delivery address payload. |
delivery_address.latRequired | number | Delivery latitude. |
delivery_address.lngRequired | number | Delivery longitude. |
delivery_address.full_addressRequired | string | Full delivery address text. |
delivery_address.cityOptional | string | City name. |
delivery_address.stateOptional | string | State or province. |
delivery_address.countryOptional | string | Country name or code. |
delivery_address.postal_codeOptional | string | Postal or ZIP code. |
delivery_address.address_line1Optional | string | Address line 1. |
delivery_address.address_line2Optional | string | Address line 2. |
delivery_address.contact_infoOptional | object | Receiver contact details. |
delivery_address.contact_info.full_nameOptional | string | Contact full name. |
delivery_address.contact_info.phoneOptional | string | Contact phone number. |
delivery_address.contact_info.emailOptional | string | Contact email address. |
customer_id | string | Required for non-customer callers. Auto-filled from auth for customer/no-role callers. |
delivery_service_idRequired | string | Delivery service UUID. |
payer_typeRequired | string | Supported values: |
payment_methodRequired | string | Accepted values: |
external_reference_idRequired | string | External reference. When |
cod_amountOptional | number | Minimum |
notesOptional | string | General delivery notes. |
special_instructionsOptional | string | Top-level delivery handling instructions. |
scheduled_pickup_timeOptional | string | ISO 8601 datetime for planned pickup. |
scheduled_delivery_timeOptional | string | ISO 8601 datetime for planned delivery. |
receiver_user_idOptional | string | Receiver user UUID. |
unit_sizeOptional | number | Positive unit size. Measured package items take priority; when neither is available, |
package_detailsOptional | object | Used to compute unit size from measured |
package_details.special_instructionsOptional | string | Parcel-specific handling notes. |
package_details.itemsOptional | array<object> | Measured parcel items used for unit-size calculation. |
package_details.items[].nameOptional | string | Item name. |
package_details.items[].descriptionOptional | string | Item description. |
package_details.items[].quantityOptional | number | Quantity for unit-size calculation. Defaults to |
package_details.items[].weightOptional | number | Item weight. |
package_details.items[].lengthOptional | number | Item length. |
package_details.items[].widthOptional | number | Item width. |
package_details.items[].heightOptional | number | Item height. |
tracking_providerOptional | string | Tracking provider identifier. |
is_draftOptional | boolean | When |
1curl --globoff --request POST "https://staging-api.ondi.io/functions/v1/external/deliveries" \2 --header "X-API-Key: ${ONDI_TENANT_API_KEY}" \3 --header "Content-Type: application/json" \4 --data-raw '{5 "pickup_address": {6 "lat": {{pickup_lat}}, // required7 "lng": {{pickup_lng}},8 "full_address": "{{pickup_full_address}}",9 "city": "{{pickup_city}}",10 "state": "{{pickup_state}}",11 "country": "{{pickup_country}}",12 "postal_code": "{{pickup_postal_code}}",13 "contact_info": {14 "full_name": "{{sender_name}}",15 "phone": "{{sender_phone}}",16 "email": "{{sender_email}}"17 }18 },19 "delivery_address": {20 "lat": {{delivery_lat}},21 "lng": {{delivery_lng}},22 "full_address": "{{delivery_full_address}}",23 "city": "{{delivery_city}}",24 "state": "{{delivery_state}}",25 "country": "{{delivery_country}}",26 "postal_code": "{{delivery_postal_code}}",27 "contact_info": {28 "full_name": "{{receiver_name}}",29 "phone": "{{receiver_phone}}",30 "email": "{{receiver_email}}"31 }32 },33 "customer_id": "{{customer_id}}",34 "delivery_service_id": "{{delivery_service_id}}",35 "payer_type": "sender",36 "payment_method": "wallet",37 "cod_amount": 0,38 "notes": "Package delivery",39 "external_reference_id": "EXT-REF-123",40 "is_draft": true,41 "scheduled_pickup_time": "2024-01-15T10:00:00Z",42 "scheduled_delivery_time": "2024-01-15T14:00:00Z",43 "receiver_user_id": "{{receiver_user_id}}",44 "unit_size": 1,45 "tracking_provider": "internal",46 "package_details": {47 "special_instructions": "Handle with care",48 "items": [49 {50 "name": "{{item_name}}",51 "description": "{{item_description}}",52 "quantity": 1,53 "weight": 2.5,54 "length": 30,55 "width": 20,56 "height": 1557 }58 ]59 },60 "special_instructions": "Handle with care"61}'Replace {{placeholders}} with your own values before sending.
Response examples
1{2 "success": true,3 "error": null,4 "message": "Delivery created successfully",5 "delivery": {6 "id": "DEL-000001",7 "tenant_id": "tenant-uuid",8 "external_reference_id": "EXT-REF-123",9 "parent_delivery_id": null,10 "assignment_id": null,11 "requires_warehouse_staging": false,12 "staging_warehouse_id": null,13 "staging_warehouse": null,14 "pickup_location": {15 "lat": 40.7128,16 "lng": -74.006,17 "latitude": 40.7128,18 "longitude": -74.006,19 "full_address": "123 Main St, New York, NY 10001",20 "city": "New York",21 "state": "NY",22 "country": "US",23 "postal_code": "10001",24 "zone_id": "pickup-zone-uuid",25 "zone_name": "Downtown Zone",26 "zone_code": "DT",27 "contact_info": {28 "full_name": "John Sender",29 "phone": "+1-555-0100",30 "email": "sender@example.com",31 "zone_name": "Downtown Zone",32 "zone_code": "DT"33 }34 },35 "delivery_location": {36 "lat": 40.7589,37 "lng": -73.9851,38 "latitude": 40.7589,39 "longitude": -73.9851,40 "full_address": "456 Broadway, New York, NY 10013",41 "city": "New York",42 "state": "NY",43 "country": "US",44 "postal_code": "10013",45 "zone_id": "delivery-zone-uuid",46 "zone_name": "Midtown Zone",47 "zone_code": "MT",48 "contact_info": {49 "full_name": "Jane Receiver",50 "phone": "+1-555-0200",51 "email": "receiver@example.com",52 "zone_name": "Midtown Zone",53 "zone_code": "MT"54 }55 },56 "pickup_warehouse_id": null,57 "dropoff_warehouse_id": null,58 "pickup_warehouse": null,59 "dropoff_warehouse": null,60 "assignment": null,61 "cod": null,62 "delivery_services": {63 "id": "service-uuid",64 "name": "Standard Delivery",65 "description": "Standard delivery",66 "image_url": "https://cdn.example.com/services/standard.png",67 "delivery_type": {68 "id": "delivery-type-uuid",69 "name": "Delivery"70 }71 },72 "customer": {73 "id": "customer-uuid",74 "full_name": "Customer Name",75 "avatar_url": null,76 "phone": "+1-555-0000"77 },78 "driver": {79 "id": null,80 "full_name": null,81 "avatar_url": null,82 "phone": null,83 "latitude": null,84 "longitude": null85 },86 "vehicle": null,87 "payment_status": "pending",88 "payer_type": "sender",89 "cod_amount": 0,90 "delivery_fee": 15.5,91 "payment_method": "wallet",92 "status": "draft",93 "open_exception": null,94 "has_open_exception": false,95 "sequence_plan": null,96 "carrier_type": "internal",97 "is_parent": false,98 "direction": null,99 "pickup_zone_id": "pickup-zone-uuid",100 "dropoff_zone_id": "delivery-zone-uuid",101 "unit_size": 1,102 "notes": "Package delivery",103 "special_instructions": "Handle with care",104 "package_details": {105 "special_instructions": "Handle with care",106 "items": [107 {108 "name": "Item 1",109 "description": "Widget",110 "quantity": 1,111 "weight": 2.5,112 "length": 30,113 "width": 20,114 "height": 15115 }116 ]117 },118 "quantity": 1,119 "delivery_code": "DEL-000001",120 "barcode": "DEL-000001",121 "track_order": "https://staging-app.ondi.io/tracking?code=DEL-000001",122 "customer_receipt_url": null,123 "status_updated_at": "2024-01-15T08:30:00Z",124 "proofs": {125 "pickup": [],126 "delivery": []127 },128 "shipment_label_url": null,129 "shipment_label_status": null,130 "status_updates": null,131 "created_at": "2024-01-15T08:00:00Z",132 "updated_at": "2024-01-15T08:00:00Z",133 "delivery_flow": null134 }135}List Deliveries
/external/deliveriesList normal deliveries visible to the authenticated external caller. External customers are always restricted to deliveries whose customer ID matches the authenticated user; a supplied customer_id cannot override this ownership scope. The default list includes draft and other active deliveries. Delivered deliveries remain excluded by default unless requested with status=delivered or is_completed=true.
Tenant scope comes from the authenticated context, with tenant_id query fallback where supported.
Success response
{ success, error, message, currency_code, deliveries, page, limit, total }. Each delivery object also includes currency_code for money fields such as delivery_fee and cod_amount. Deliveries are loaded from delivery_list_view, then enriched for the external contract.
Frontend integration contract
read snake_case response fields only. Use delivery.delivery_services.delivery_type for delivery type, delivery.track_order for the public tracking page, delivery.customer_receipt_url when non-null, and delivery.status_updated_at for the last status change timestamp.
External delivery response enrichment
Top-level response includes
currency_code. Each delivery object also includescurrency_code.delivery.track_orderis the public tracking page URL built as {track_url}/tracking?code={delivery_code};track_urlresolves from tenanttrack_orders.track_url, thenWEB_URLsecret, then systemexternal_links.delivery_customer_portal_url, then https://staging-app.ondi.io.delivery.customer_receipt_urlis a signed receipt URL when a successful delivery payment receipt exists; otherwise null.delivery.status_updated_atis derived from the lateststatus_updates[].timestamp, falling back toupdated_at.delivery.delivery_servicesincludes description andimage_urlwhen available.delivery.proofsis grouped as { pickup: [], delivery: [] } using OnDi proof records.delivery.proof_of_pickupanddelivery.proof_of_deliveryare aggregate proof summaries when captured, shaped as { signature, photos }.delivery.failure_reasonis populated fromdelivery_failure_reasonwhen present.Top-level
delivery_typeis not returned; usedelivery.delivery_services.delivery_type.payment_methodis documented as wallet|cash.
Query parameters
| Parameter | Type | Description |
|---|---|---|
pageOptional | string | Page number for pagination (Optional, default: 1) Example: |
limitOptional | string | Number of deliveries per page (Optional, default: 10) Example: |
statusOptional | string | Filter by delivery status, including draft, pending, assigned, |
customer_idOptional | string | Filter by customer ID (Optional) |
searchOptional | string | Search in delivery details (Optional) |
driver_idOptional | string | Filter by driver ID (Optional) |
zone_idsOptional | string | Filter by zone IDs (comma-separated) (Optional) |
payer_typeOptional | string | Filter by payer type (sender/receiver) (Optional) |
is_completedOptional | string | Filter by completion status (true/false) (Optional) |
payment_methodOptional | string | Filter by payment method (wallet/cod/card) (Optional) |
payment_statusOptional | string | Filter by payment status (Optional) |
delivery_service_idOptional | string | Filter by delivery service ID (Optional) |
start_dateOptional | string | Filter by start date (ISO format) (Optional) |
end_dateOptional | string | Filter by end date (ISO format) (Optional) |
assignment_statusOptional | string | Filter by assignment status (Optional) |
Headers
| Parameter | Type | Description |
|---|---|---|
X-API-Key | string | Example: |
1curl --globoff --request GET "https://staging-api.ondi.io/functions/v1/external/deliveries?page=1&limit=10" \2 --header "X-API-Key: ${ONDI_TENANT_API_KEY}"Response examples
1{2 "success": true,3 "error": null,4 "message": "Deliveries retrieved successfully",5 "currency_code": "IQD",6 "deliveries": [7 {8 "id": "DEL-000001",9 "status": "pending",10 "tenant_id": "tenant-uuid",11 "external_reference_id": "EXT-REF-123",12 "customer_id": "customer-uuid",13 "delivery_service_id": "service-uuid",14 "payer_type": "sender",15 "payment_method": "wallet",16 "delivery_fee": 15.5,17 "cod_amount": 0,18 "currency_code": "IQD",19 "notes": "Package delivery",20 "unit_size": 1,21 "tracking_provider": "internal",22 "carrier_type": "internal",23 "pickup_warehouse_id": null,24 "dropoff_warehouse_id": null,25 "pickup_location_id": "address-uuid-1",26 "delivery_location_id": "address-uuid-2",27 "receiver_user_id": null,28 "assigned_carrier_id": null,29 "assigned_to_user_id": null,30 "package_details": {31 "special_instructions": "Handle with care",32 "items": [33 {34 "name": "Item 1",35 "description": "Widget",36 "quantity": 1,37 "weight": 2.538 }39 ]40 },41 "scheduled_pickup_time": "2024-01-15T10:00:00Z",42 "scheduled_delivery_time": "2024-01-15T14:00:00Z",43 "started_at": null,44 "picked_up_at": null,45 "delivered_at": null,46 "cancelled_at": null,47 "delivery_failure_reason": null,48 "created_at": "2024-01-15T08:00:00Z",49 "updated_at": "2024-01-15T08:00:00Z",50 "barcode": "DEL-000001",51 "pickup_location": {52 "id": "address-uuid-1",53 "full_address": "123 Main St, New York, NY 10001",54 "latitude": 40.7128,55 "longitude": -74.006,56 "city": "New York",57 "state": "NY",58 "country": "US",59 "postal_code": "10001",60 "contact_info": {61 "full_name": "John Sender",62 "phone": "+1-555-0100",63 "email": "sender@example.com"64 }65 },66 "delivery_location": {67 "id": "address-uuid-2",68 "full_address": "456 Broadway, New York, NY 10013",69 "latitude": 40.7589,70 "longitude": -73.9851,71 "city": "New York",72 "state": "NY",73 "country": "US",74 "postal_code": "10013",75 "contact_info": {76 "full_name": "Jane Receiver",77 "phone": "+1-555-0200",78 "email": "receiver@example.com"79 }80 },81 "delivery_code": "DEL-000001"82 }83 ],84 "page": 1,85 "limit": 10,86 "total": 2587}Get Delivery
/external/deliveries/{{delivery_code}}Retrieve one normal delivery by public delivery code, UUID, or barcode. External customers can retrieve only their own delivery; another customer’s delivery returns 404.
The path value is the public delivery code by default. UUIDs still work for legacy callers.
Tenant scope comes from the authenticated context, with tenant_id query fallback where supported.
Success response
{ success, error, message, currency_code, delivery }. The delivery object also includes currency_code for money fields such as delivery_fee and cod_amount. Delivery is loaded from delivery_list_view, then enriched for the external contract.
Responses
200 success, 404 delivery_not_found, 500 internal_server_error for a database/query failure.
Frontend integration contract
read snake_case response fields only. Use delivery.delivery_services.delivery_type for delivery type, delivery.track_order for the public tracking page, delivery.customer_receipt_url when non-null, and delivery.status_updated_at for the last status change timestamp.
External delivery response enrichment
Top-level response includes
currency_code. Each delivery object also includescurrency_code.delivery.idis the public delivery code and matchesdelivery.delivery_code.delivery.track_orderis the public tracking page URL built as {track_url}/tracking?code={delivery_code};track_urlresolves from tenanttrack_orders.track_url, thenWEB_URLsecret, then systemexternal_links.delivery_customer_portal_url, then https://staging-app.ondi.io.delivery.customer_receipt_urlis a signed receipt URL when a successful delivery payment receipt exists; otherwise null.delivery.status_updated_atis derived from the lateststatus_updates[].timestamp, falling back toupdated_at.delivery.delivery_servicesincludes description andimage_urlwhen available.delivery.proofsis grouped as { pickup: [], delivery: [] } using OnDi proof records.delivery.proof_of_pickupanddelivery.proof_of_deliveryare aggregate proof summaries when captured, shaped as { signature, photos }.delivery.failure_reasonis populated fromdelivery_failure_reasonwhen present.Top-level
delivery_typeis not returned; usedelivery.delivery_services.delivery_type.payment_methodis documented as wallet|cash.
Path parameters
| Parameter | Type | Description |
|---|---|---|
delivery_codeRequired | string | Variable used inside the request path. |
Query parameters
| Parameter | Type | Description |
|---|---|---|
filter_typeOptional | string | Optional override: id, Example: |
Headers
| Parameter | Type | Description |
|---|---|---|
X-API-Key | string | Example: |
1curl --globoff --request GET "https://staging-api.ondi.io/functions/v1/external/deliveries/${DELIVERY_CODE}" \2 --header "X-API-Key: ${ONDI_TENANT_API_KEY}"Response examples
1{2 "success": true,3 "error": null,4 "message": "Delivery retrieved successfully",5 "currency_code": "IQD",6 "delivery": {7 "id": "DEL-000001",8 "status": "pending",9 "tenant_id": "tenant-uuid",10 "external_reference_id": "EXT-REF-123",11 "customer_id": "customer-uuid",12 "delivery_service_id": "service-uuid",13 "payer_type": "sender",14 "payment_method": "wallet",15 "delivery_fee": 15.5,16 "cod_amount": 0,17 "currency_code": "IQD",18 "notes": "Package delivery",19 "unit_size": 1,20 "tracking_provider": "internal",21 "carrier_type": "internal",22 "pickup_warehouse_id": null,23 "dropoff_warehouse_id": null,24 "pickup_location_id": "address-uuid-1",25 "delivery_location_id": "address-uuid-2",26 "receiver_user_id": null,27 "assigned_carrier_id": null,28 "assigned_to_user_id": null,29 "package_details": {30 "special_instructions": "Handle with care",31 "items": [32 {33 "name": "Item 1",34 "description": "Widget",35 "quantity": 1,36 "weight": 2.537 }38 ]39 },40 "scheduled_pickup_time": "2024-01-15T10:00:00Z",41 "scheduled_delivery_time": "2024-01-15T14:00:00Z",42 "started_at": null,43 "picked_up_at": null,44 "delivered_at": null,45 "cancelled_at": null,46 "delivery_failure_reason": null,47 "created_at": "2024-01-15T08:00:00Z",48 "updated_at": "2024-01-15T08:00:00Z",49 "barcode": "DEL-000001",50 "pickup_location": {51 "id": "address-uuid-1",52 "full_address": "123 Main St, New York, NY 10001",53 "latitude": 40.7128,54 "longitude": -74.006,55 "city": "New York",56 "state": "NY",57 "country": "US",58 "postal_code": "10001",59 "contact_info": {60 "full_name": "John Sender",61 "phone": "+1-555-0100",62 "email": "sender@example.com"63 }64 },65 "delivery_location": {66 "id": "address-uuid-2",67 "full_address": "456 Broadway, New York, NY 10013",68 "latitude": 40.7589,69 "longitude": -73.9851,70 "city": "New York",71 "state": "NY",72 "country": "US",73 "postal_code": "10013",74 "contact_info": {75 "full_name": "Jane Receiver",76 "phone": "+1-555-0200",77 "email": "receiver@example.com"78 }79 },80 "delivery_code": "DEL-000001"81 }82}Update Delivery
/external/deliveries/{{delivery_code}}Update an external customer's owned delivery order.
Access and immutable fields
An external customer can update only an order whose
customer_idmatches the authenticated customer. A different customer's identifier returns HTTP 404.Tenant staff using this endpoint require
manage:operations:tenantormanage:module:delivery.customer_id,delivery_service_id, status, and internaldelivery_floware immutable.Use the public boolean
is_replacementto enable or disable replacement handling.Status is not accepted here; use the existing external status/confirmation endpoints.
Editable until final delivery
pickup_warehouse_idordropoff_warehouse_idpickup_location_idorpickup_locationdelivery_location_idordelivery_locationexternal_reference_id(string ornull; trimmed, and an empty string ornullclears it)cod_amount(minimum 0)payer_type(sender | receiver)payment_method(wallet | cash | cod;codis stored ascash)delivery_fee(minimum 0)unit_size(integer, minimum 1)is_replacement(boolean)notes(string ornull; send an empty string ornullto clear it)package_details, includingpackage_details.special_instructions(string handling instructions) and replacement semantics foritems[]; each item requires a name, a positive integer quantity, non-negative numeric dimensions, and a valid UUID whenidis supplied
External references keep the tenant delivery_external_reference.unique_per_customer rule. When enabled (default), another root order for the same customer cannot use the same non-empty value; the API returns HTTP 400 with a localized duplicate-reference message and field=external_reference_id.
The external API is no longer draft-only. The same rule is used by internal and external updates: an inbound P&D leg delivered to the warehouse does not lock the order, but final parent/outbound delivered locks all editable fields.
A non-null warehouse ID takes priority over the matching address ID/JSON and resolves the tenant warehouse address. Send the warehouse field as null with a replacement customer address to switch back to a normal address. A missing, cross-tenant, or address-less warehouse fails before the atomic update. Saved non-warehouse address IDs must belong to the authenticated customer. Address and unit changes reuse the fixed service to validate countries/zones and recalculate the fee. An explicit delivery_fee wins. For P&D, endpoint warehouse/address changes propagate to the matching operational leg; external reference, notes, and special instructions are stored on the parent and both operational legs in the same atomic update. If an assigned driver cannot serve the new zone, or the larger unit size exceeds assigned vehicle capacity, the API returns HTTP 409 and nothing changes.
Unchanged endpoint submissions and contact-only location updates do not trigger automatic fee recalculation when the effective address and warehouse IDs and every normalized non-contact location field match the saved endpoint. Any extra submitted location field must also match its saved value. These saves preserve the saved non-contact snapshot, including omitted legacy fields, and still run the existing address resolution, coordinate/country validation, atomic assignment checks, and finance rules. Real endpoint or resolved unit-size changes still run service/zone pricing. An explicitly supplied delivery_fee remains supported under the existing rules.
Replacement behavior
is_replacement: truemaps the logical order todelivery_flow = replacement_forward;falseremoves that classification. A child code updates its logical P&D parent.The toggle is editable until final delivery. Final completion and return creation recheck the same state atomically.
Enabling replacement while the final payment method is cash remains subject to
allow_cod_on_replacement; a forbidden change returns HTTP 400cod_not_allowed_for_replacement_orders.Disabling after the return exists returns HTTP 409
replacement_return_already_exists.A replacement return cannot be converted; submitting
is_replacementon it returns HTTP 400replacement_return_type_not_editable.A replacement forward follows normal direct/P&D update rules. Its latest addresses, package, unit size, and fixed service are used when the return is generated.
Return addresses, package details, notes, special instructions, unit size, payer type, and payment method remain editable until the return is delivered. Direct and staged return assignments receive normal zone and vehicle-capacity validation.
Every replacement return remains zero-fee and zero-COD. Non-zero values return HTTP 400
replacement_return_delivery_fee_must_be_zeroorreplacement_return_cod_must_be_zero.
Finance
Draft wallet orders resize or move their reservation atomically as fee, payer, or method changes. No wallet transaction or payment is created until confirmation; changing away from wallet releases the hold.
Non-draft sender-wallet updates refund the full previously settled fee first, then charge the full updated fee. These are two visible wallet transaction lines when both amounts are non-zero.
Pending sender cash to wallet charges the full current delivery fee.
Pending sender wallet to cash refunds the full settled wallet fee and leaves the fee pending for pickup collection.
Sender cash remains a pickup obligation before pickup; later corrections refund any full previous wallet settlement, then charge the full corrected fee through the sender wallet without rewriting historical physical cash.
Receiver cash or wallet refunds any settled sender fee and stays pending as a final-delivery obligation; no receiver collection/payment row is created by this update.
COD updates only change the future goods-collection amount; no COD collection, payment, or wallet entry is created before final delivery.
Successful delivery-fee payment rows are the settled-money source of truth; the delivery payment-status flag does not fabricate a wallet settlement.
Refund, replacement charge, payment audit, and delivery changes reconcile atomically.
Replacement-return payer or method changes move no money because its fee remains zero.
insufficient_available_balanceor insufficient wallet balance rolls back the update. A missing customer wallet returns HTTP 500.
Webhook
External-reference-only, notes-only,
package_details.special_instructions-only, and replacement-only updates do not emitorder_update; existing tracked fields remain status, COD, price, payment side, and payment method.One request emits at most one existing
order_updatewebhook when tracked fields change; P&D child synchronization emits no duplicate request.
Integration contract
When a warehouse is selected, send its non-null
pickup_warehouse_idordropoff_warehouse_id; matching address input is ignored. To deselect it, send the warehouse field asnulltogether with the replacement address.Send only changed fields and use
is_replacementfor the toggle; never senddelivery_flow. Send special instructions only aspackage_details.special_instructions; top-levelspecial_instructionsis rejected.Use the returned canonical parent
delivery;delivery_flow = replacement_forwardmeans the toggle is on.Show the toggle until final delivery, except for
delivery_flow = replacement_return, where it is read-only. Keep replacement-returndelivery_feeandcod_amountread-only at zero.Handle HTTP 400 with
field=external_reference_idas a duplicate external reference, plusinvalid_delivery_update_fields,invalid_package_details,cod_not_allowed_for_replacement_orders,replacement_return_type_not_editable, and the zero-fee/zero-COD errors.Handle HTTP 409
delivery_already_delivered,replacement_return_already_exists, driver zone/capacity errors,insufficient_available_balance,insufficient_balance, anddelivery_update_conflict.On a conflict, keep the previous local order; reload before retrying.
Path parameters
| Parameter | Type | Description |
|---|---|---|
delivery_codeRequired | string | Variable used inside the request path. |
Headers
| Parameter | Type | Description |
|---|---|---|
X-API-Key | string | Example: |
Content-Type | string | Example: |
Body parameters
| Parameter | Type | Description |
|---|---|---|
pickup_warehouse_id | string | — |
pickup_location | object | — |
pickup_location.full_address | string | — |
pickup_location.latitude | number | — |
pickup_location.longitude | number | — |
pickup_location.city | string | — |
pickup_location.country | string | — |
dropoff_warehouse_id | null | — |
delivery_location_id | string | — |
external_reference_id | string | — |
cod_amount | number | — |
payer_type | string | — |
Show 15 more body fields
Additional body parameters
| Parameter | Type | Description |
|---|---|---|
payment_method | string | — |
delivery_fee | number | — |
unit_size | number | — |
is_replacement | boolean | — |
package_details | object | — |
package_details.special_instructions | string | — |
package_details.items | array<object> | — |
package_details.items.name | string | — |
package_details.items.description | string | — |
package_details.items.quantity | number | — |
package_details.items.weight | number | — |
package_details.items.length | number | — |
package_details.items.width | number | — |
package_details.items.height | number | — |
notes | string | — |
1curl --globoff --request PUT "https://staging-api.ondi.io/functions/v1/external/deliveries/${DELIVERY_CODE}" \2 --header "X-API-Key: ${ONDI_TENANT_API_KEY}" \3 --header "Content-Type: application/json" \4 --data-raw '{5 "pickup_warehouse_id": "pickup-warehouse-uuid",6 "pickup_location": {7 "full_address": "Updated pickup address",8 "latitude": 36.191,9 "longitude": 44.01,10 "city": "Erbil",11 "country": "Iraq"12 },13 "dropoff_warehouse_id": null,14 "delivery_location_id": "dropoff-address-uuid",15 "external_reference_id": "CUSTOMER-ORDER-456",16 "cod_amount": 25,17 "payer_type": "receiver",18 "payment_method": "cod",19 "delivery_fee": 31,20 "unit_size": 2,21 "is_replacement": true,22 "package_details": {23 "special_instructions": "Keep upright and avoid heat",24 "items": [25 {26 "name": "Item 1",27 "description": "Updated item",28 "quantity": 1,29 "weight": 3.5,30 "length": 35,31 "width": 25,32 "height": 2033 }34 ]35 },36 "notes": "Call the receiver before delivery"37}'Response examples
1{2 "success": true,3 "message": "Delivery updated successfully",4 "currency_code": "IQD",5 "delivery": {6 "id": "parent-delivery-uuid",7 "delivery_code": "2123",8 "status": "in_transit",9 "delivery_flow": "replacement_forward",10 "external_reference_id": "CUSTOMER-ORDER-456",11 "pickup_warehouse_id": "pickup-warehouse-uuid",12 "dropoff_warehouse_id": null,13 "customer_id": "customer-uuid",14 "delivery_service_id": "service-uuid",15 "pickup_location": {16 "full_address": "Updated pickup address",17 "latitude": 36.191,18 "longitude": 44.0119 },20 "delivery_location_id": "dropoff-address-uuid",21 "payer_type": "receiver",22 "payment_method": "cash",23 "delivery_fee": 31,24 "cod_amount": 25,25 "unit_size": 2,26 "package_details": {27 "items": [28 {29 "name": "Item 1",30 "quantity": 131 }32 ]33 },34 "updated_at": "2026-07-21T12:00:00Z",35 "notes": "Call the receiver before delivery"36 }37}Update Delivery Status By Code
/external/deliveries/code/{{delivery_code}}/statusUpdate a normal delivery status using delivery_code instead of delivery ID. The authenticated external caller can update only deliveries they own.
Behavior
Uses the same status workflow as the internal delivery endpoint.
Lookup is by
delivery_code, but status changes are applied only when the delivery belongs to the authenticated external caller.On success, the response is
{ success, error, message, currency_code, delivery }anddeliveryis loaded fromdelivery_list_view, then enriched for the external contract.
Responses
200 success, 400 validation failure, 404 delivery_not_found, 409 invalid transition / business conflict.
External delivery response enrichment
Top-level response includes
currency_code. Each delivery object also includescurrency_code.delivery.track_orderis the public tracking page URL built as{track_url}/tracking?code={delivery_code};track_urlresolves from tenanttrack_orders.track_url, then theWEB_URLsecret, then systemexternal_links.delivery_customer_portal_url, thenhttps://staging-app.ondi.io.delivery.customer_receipt_urlis a signed receipt URL when a successful delivery payment receipt exists; otherwisenull.delivery.status_updated_atis derived from the lateststatus_updates[].timestamp, falling back toupdated_at.delivery.delivery_servicesincludesdescriptionandimage_urlwhen available.delivery.proofsis grouped as{ pickup: [], delivery: [] }using OnDi proof records.delivery.proof_of_pickupanddelivery.proof_of_deliveryare aggregate proof summaries when captured, shaped as{ signature, photos }.delivery.failure_reasonis populated fromdelivery_failure_reasonwhen present.Top-level
delivery_typeis not returned; usedelivery.delivery_services.delivery_type.Response
payment_methodis documented aswallet | cash.
Path parameters
| Parameter | Type | Description |
|---|---|---|
delivery_codeRequired | string | Variable used inside the request path. |
Headers
| Parameter | Type | Description |
|---|---|---|
X-API-Key | string | Example: |
Content-Type | string | Example: |
Body parameters
| Parameter | Type | Description |
|---|---|---|
target_delivery_status | string | One of |
reasonOptional | string | Transition reason. If |
driver_id | string | Required when the requested status needs an assignment, the delivery has no active assignment, and tenant settings do not define |
notesOptional | string | Timeline / workflow notes. |
proof_of_deliveryOptional | object | Proof payload for delivered flows. Only |
proof_of_delivery.signature_urlOptional | string | Signature asset URL. Some delivered flows may require it based on tenant settings. |
proof_of_delivery.photo_urlsOptional | array<string> | Proof photo URLs. Some delivered flows may require at least one photo based on tenant settings. |
bin_idOptional | string | Internal bin identifier passed through when relevant to the status workflow. |
returned_itemsOptional | string | Used for return flows. (string form): Inventory item ID. The backend converts it to |
returned_items[].id | string | Required when object form is used | |
returned_items[].reason | string | Required when object form is used | |
1curl --globoff --request POST "https://staging-api.ondi.io/functions/v1/external/deliveries/code/${DELIVERY_CODE}/status" \2 --header "X-API-Key: ${ONDI_TENANT_API_KEY}" \3 --header "Content-Type: application/json" \4 --data-raw '{5 "target_delivery_status": "delivered",6 "reason": "Delivery completed successfully",7 "driver_id": "{{driver_id}}",8 "notes": "Delivered by external integration",9 "proof_of_delivery": {10 "signature_url": "{{signature_url}}",11 "photo_urls": [12 "{{proof_photo_url_1}}"13 ]14 }15}'Replace {{placeholders}} with your own values before sending.
Response examples
1{2 "success": true,3 "error": null,4 "message": "Delivery status updated successfully",5 "delivery": {6 "id": "DEL-000001",7 "status": "delivered",8 "tenant_id": "tenant-uuid",9 "external_reference_id": "EXT-REF-123",10 "customer_id": "customer-uuid",11 "delivery_service_id": "service-uuid",12 "payer_type": "sender",13 "payment_method": "wallet",14 "delivery_fee": 15.5,15 "cod_amount": 0,16 "notes": "Package delivery",17 "unit_size": 1,18 "tracking_provider": "internal",19 "carrier_type": "internal",20 "pickup_warehouse_id": null,21 "dropoff_warehouse_id": null,22 "pickup_location_id": "address-uuid-1",23 "delivery_location_id": "address-uuid-2",24 "receiver_user_id": null,25 "assigned_carrier_id": null,26 "assigned_to_user_id": null,27 "package_details": {28 "special_instructions": "Handle with care"29 },30 "scheduled_pickup_time": "2024-01-15T10:00:00Z",31 "scheduled_delivery_time": "2024-01-15T14:00:00Z",32 "started_at": null,33 "picked_up_at": null,34 "delivered_at": null,35 "cancelled_at": null,36 "delivery_failure_reason": null,37 "created_at": "2024-01-15T08:00:00Z",38 "updated_at": "2024-01-15T08:30:00Z",39 "barcode": "DEL-000001",40 "delivery_code": "DEL-000001"41 }42}Delete Delivery (Legacy Admin Cancellation)
/external/deliveries/{{delivery_code}}Legacy tenant-admin cancellation endpoint. The delivery record is retained. This endpoint delegates to the canonical atomic logical-order cancellation flow, so a parent or operational child resolves to the root, the order family and active work are cancelled consistently, and paid family delivery fees or collected COD are refunded exactly once to the sender wallet. Separate replacement-return orders are excluded. Requires manage:operations:tenant or system-admin access; customer API keys must use POST /external/deliveries/{delivery_code}/cancel.
Responses
200 success, 403 unauthorized, 404 delivery not found, 409 terminal logical-order conflict. Admin callers may cancel assigned work.
Frontend integration contract
treat success as cancellation, not physical deletion. Keep the record and render it with cancelled status. Prefer the explicit POST cancellation endpoint for new integrations.
Path parameters
| Parameter | Type | Description |
|---|---|---|
delivery_codeRequired | string | Variable used inside the request path. |
Headers
| Parameter | Type | Description |
|---|---|---|
X-API-Key | string | Example: |
1curl --globoff --request DELETE "https://staging-api.ondi.io/functions/v1/external/deliveries/${DELIVERY_CODE}" \2 --header "X-API-Key: ${ONDI_TENANT_API_KEY}"Response examples
1{2 "success": true,3 "message": "Delivery deleted successfully"4}Cancel Delivery
/external/deliveries/{{delivery_code}}/cancelCancel a delivery owned by the authenticated external customer. Ownership is enforced before cancellation even if the auth context contains an accidental staff permission; another customer’s delivery returns 404. Cancellation is transactionally blocked with HTTP 409 cannot_cancel_delivery_after_driver_assignment once an assignment in the logical order is active, and with HTTP 409 cannot_cancel_delivery_in_terminal_status after final delivery or another terminal logical state.
Empty body: Allowed | N/A | This endpoint accepts no JSON body.
Staged/P&D behavior
the parent or any operational child identifier resolves to the canonical parent. A completed inbound warehouse leg does not block cancellation while final delivery remains incomplete. Parent, non-terminal operational children, assignments, receiving work, handoffs, and eligible picking work are cancelled atomically. Paid family delivery fees and collected COD are refunded once to the sender wallet. Separate replacement-return orders are excluded.
Responses
200 success, 400 validation failure, 403 unauthorized, 404 delivery_not_found, 409 assigned-work or terminal logical-order conflict.
External delivery response enrichment
Top-level response includes
currency_code. Each delivery object also includescurrency_code.The returned delivery is the canonical logical root;
delivery.idis its public delivery code and matchesdelivery.delivery_code.delivery.track_orderis the public tracking page URL built as{track_url}/tracking?code={delivery_code};track_urlresolves from tenanttrack_orders.track_url, then theWEB_URLsecret, then systemexternal_links.delivery_customer_portal_url, thenhttps://staging-app.ondi.io.delivery.customer_receipt_urlis a signed receipt URL when a successful delivery payment receipt exists; otherwisenull.delivery.status_updated_atis derived from the lateststatus_updates[].timestamp, falling back toupdated_at.delivery.delivery_servicesincludesdescriptionandimage_urlwhen available.delivery.proofsis grouped as{ pickup: [], delivery: [] }using OnDi proof records.delivery.proof_of_pickupanddelivery.proof_of_deliveryare aggregate proof summaries when captured, shaped as{ signature, photos }.delivery.failure_reasonis populated fromdelivery_failure_reasonwhen present.Top-level
delivery_typeis not returned; usedelivery.delivery_services.delivery_type.Response
payment_methodis documented aswallet | cash.
Path parameters
| Parameter | Type | Description |
|---|---|---|
delivery_codeRequired | string | Variable used inside the request path. |
Headers
| Parameter | Type | Description |
|---|---|---|
X-API-Key | string | Example: |
Body parameters
| Parameter | Type | Description |
|---|---|---|
failure_reasonOptional | string | Reason stored in |
notesOptional | string | Additional timeline notes. |
1curl --globoff --request POST "https://staging-api.ondi.io/functions/v1/external/deliveries/${DELIVERY_CODE}/cancel" \2 --header "X-API-Key: ${ONDI_TENANT_API_KEY}" \3 --header "Content-Type: application/json" \4 --data-raw '{5 "failure_reason": "Customer changed mind",6 "notes": "Order cancelled per customer request"7}'Response examples
1{2 "success": true,3 "error": null,4 "message": "Delivery cancelled successfully",5 "delivery": {6 "id": "DEL-000001",7 "status": "cancelled",8 "tenant_id": "tenant-uuid",9 "external_reference_id": "EXT-REF-123",10 "customer_id": "customer-uuid",11 "delivery_service_id": "service-uuid",12 "payer_type": "sender",13 "payment_method": "wallet",14 "delivery_fee": 15.5,15 "cod_amount": 0,16 "notes": "Order cancelled per customer request",17 "unit_size": 1,18 "tracking_provider": "internal",19 "carrier_type": "internal",20 "pickup_warehouse_id": null,21 "dropoff_warehouse_id": null,22 "pickup_location_id": "address-uuid-1",23 "delivery_location_id": "address-uuid-2",24 "receiver_user_id": null,25 "assigned_carrier_id": null,26 "assigned_to_user_id": null,27 "package_details": {28 "special_instructions": "Handle with care"29 },30 "scheduled_pickup_time": "2024-01-15T10:00:00Z",31 "scheduled_delivery_time": "2024-01-15T14:00:00Z",32 "started_at": null,33 "picked_up_at": null,34 "delivered_at": null,35 "cancelled_at": "2024-01-15T09:00:00Z",36 "delivery_failure_reason": "Customer changed mind",37 "created_at": "2024-01-15T08:00:00Z",38 "updated_at": "2024-01-15T09:00:00Z",39 "barcode": "DEL-000001",40 "delivery_code": "DEL-000001"41 }42}Confirm Delivery
/external/deliveries/{{delivery_code}}/confirmConfirm (execute) a draft delivery by transitioning its status from 'draft' to 'pending'. The delivery becomes available for assignment and execution.
This endpoint has no request body; it is fully driven by the delivery's current status.
Business Rules
External customers can confirm only a delivery they own; another customer’s delivery returns 404
Delivery must have status = 'draft'
A positive wallet fee must already be reserved; confirmation captures it and creates the delivery-fee transaction/payment atomically
For staged parents, the outbound child must not be in 'draft' status (already executed)
Responses
200 success, 409 invalid_status_transition / insufficient_available_balance / wallet reservation conflict, 404 delivery_not_found. The reservation contract returns no currency; use tenant configuration.
External delivery response enrichment
Top-level response includes
currency_code. Each delivery object also includescurrency_code.delivery.idis the public delivery code and matchesdelivery.delivery_code.delivery.track_orderis the public tracking page URL built as {track_url}/tracking?code={delivery_code};track_urlresolves from tenanttrack_orders.track_url, thenWEB_URLsecret, then systemexternal_links.delivery_customer_portal_url, then https://staging-app.ondi.io.delivery.customer_receipt_urlis a signed receipt URL when a successful delivery payment receipt exists; otherwise null.delivery.status_updated_atis derived from the lateststatus_updates[].timestamp, falling back toupdated_at.delivery.delivery_servicesincludes description andimage_urlwhen available.delivery.proofsis grouped as { pickup: [], delivery: [] } using OnDi proof records.delivery.proof_of_pickupanddelivery.proof_of_deliveryare aggregate proof summaries when captured, shaped as { signature, photos }.delivery.failure_reasonis populated fromdelivery_failure_reasonwhen present.Top-level
delivery_typeis not returned; usedelivery.delivery_services.delivery_type.payment_methodis documented as wallet|cash.
Path parameters
| Parameter | Type | Description |
|---|---|---|
delivery_codeRequired | string | Variable used inside the request path. |
Headers
| Parameter | Type | Description |
|---|---|---|
X-API-Key | string | Example: |
1curl --globoff --request POST "https://staging-api.ondi.io/functions/v1/external/deliveries/${DELIVERY_CODE}/confirm" \2 --header "X-API-Key: ${ONDI_TENANT_API_KEY}"Response examples
1{2 "success": true,3 "error": null,4 "message": "Delivery status updated successfully",5 "delivery": {6 "id": "DEL-000001",7 "status": "pending"8 }9}