Skip to main content

Rate Quotes

Get a price estimate before creating a shipment. The rate endpoint resolves distance, picks a vehicle, and returns itemized pricing — without writing to the database or debiting your wallet.

Call this whenever you want to show a delivery cost to your end customer or check affordability before committing.

POST /merchant/shipments/rates

curl -X POST https://api.dodo.co.tz/api/v1/merchant/shipments/rates \
-H "X-API-Key: $DODO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"pickup_latitude": -6.7924,
"pickup_longitude": 39.2083,
"dropoff_latitude": -6.7600,
"dropoff_longitude": 39.2400,
"items": [
{
"description": "Food order",
"weight_kg": 2.5,
"quantity": 1,
"length_cm": 25,
"width_cm": 25,
"height_cm": 15
}
]
}'

Request

You must provide either GPS coordinates or an address string for each end of the route. Coordinates are strongly preferred — address strings are geocoded and may return imprecise locations.

FieldTypeRequiredDescription
pickup_latitudenumberconditionalPickup latitude (-90 to 90)
pickup_longitudenumberconditionalPickup longitude (-180 to 180)
pickup_addressstringconditionalPickup address (used if coords omitted)
dropoff_latitudenumberconditionalDropoff latitude
dropoff_longitudenumberconditionalDropoff longitude
dropoff_addressstringconditionalDropoff address (used if coords omitted)
itemsarrayyes1–50 items, see below
vehicle_type_idintegernoForce a specific vehicle (otherwise auto-selected by weight)

Item fields

Every shipment has at least one item. For same-city food/courier work, only description, weight_kg, and quantity are required. For regional or fragile items, supply dimensions so chargeable weight can be computed.

FieldTypeRequiredDescription
descriptionstringyesWhat the item is (2–500 chars)
weight_kgnumberyesActual weight in kilograms (0 < w ≤ 500)
quantityintegeryesNumber of units (1–100)
length_cmnumbernoLength in centimeters
width_cmnumbernoWidth in centimeters
height_cmnumbernoHeight in centimeters
is_fragilebooleannoTriggers fragile surcharge — default false
declared_valuenumbernoInsured value in TZS — default null
Chargeable weight

When dimensions are provided, the engine computes volumetric weight = (L × W × H) / 5000 per item. The chargeable weight is max(actual_weight, volumetric_weight) — that's what regional transit charges against.

For same-city deliveries, weight only affects vehicle selection.

Response

{
"shipment_type": "same_city",
"vehicle_id": 1,
"vehicle_name": "Motorcycle",
"first_mile_fee": 3727,
"transit_fee": 0,
"last_mile_fee": 0,
"handling_fee": 0,
"service_fee": 373,
"fragile_surcharge": 0,
"total_amount": 4100,
"distance_km": 5.2,
"estimated_delivery_hours": 1,
"origin_hub": null,
"destination_hub": null
}
FieldTypeDescription
shipment_typestring"same_city" or "regional"
vehicle_idintegerThe vehicle the engine selected
vehicle_namestringDisplay name (e.g. "Motorcycle", "Van")
first_mile_feeintegerPickup → origin hub (regional) or full leg (same-city), TZS
transit_feeintegerHub-to-hub transit, TZS — 0 for same-city
last_mile_feeintegerDestination hub → receiver, TZS — 0 for same-city
handling_feeintegerHub handling, TZS — 0 for same-city
service_feeinteger10% platform fee, TZS
fragile_surchargeinteger15% surcharge if any item is fragile, TZS
total_amountintegerSum — what your wallet will be debited
distance_kmnumberTotal road distance (Google Distance Matrix, Haversine fallback)
estimated_delivery_hoursintegerEnd-to-end estimate
origin_hubstring | nullHub name (regional only)
destination_hubstring | nullHub name (regional only)
tip

total_amount is the only number to surface to your end customer. The fee breakdown is for your accounting records.

Same-city vs regional pricing

The engine detects the type from distance:

Same-city (under ~50 km)

first_mile_fee  = max(base_fee + distance × price_per_km, minimum_fee)
service_fee = 10% of first_mile_fee
total_amount = first_mile_fee + service_fee + fragile_surcharge

Vehicle base fees and per-km rates are in Vehicle Types.

Regional (50+ km)

first_mile_fee  = pickup rider fee (sender → origin hub)
transit_fee = base_transit_rate + (chargeable_weight × per_kg_rate)
last_mile_fee = delivery rider fee (destination hub → receiver)
handling_fee = 2,000 TZS flat
fragile_surcharge = 15% of (first_mile + transit + last_mile + handling) if any item fragile
service_fee = 10% of all fees above
total_amount = sum of all fees

Errors

StatusDetail
400"Either coordinates or an address must be provided for pickup."
400"Could not geocode dropoff address: '…'." — bad address string
400"No transit route between origin and destination hubs." — regional path not yet served
401Missing or invalid API key