Skip to main content

Vehicle Types

Vehicles determine pricing tiers and what kinds of items can be moved. You normally don't pick a vehicle yourself — the rate engine auto-selects based on item weight and dimensions. This page is for cases where you need to surface choices to your customer or override the selection.

How vehicle selection works

When you call POST /merchant/shipments/rates or POST /merchant/shipments:

  1. The engine sums the chargeable weight across all items.
  2. It picks the cheapest active vehicle whose max_weight_kg covers the total.
  3. The chosen vehicle_id and vehicle_name come back in the response.

To override the auto-selection, pass vehicle_type_id on the request. Use this when:

  • You want to force a larger vehicle (e.g. you know the package is awkward but light)
  • You're letting the end customer choose (e.g. "Send by bicycle / motorcycle / car")

List vehicle types

GET /vehicle-types

Public endpoint — no authentication required. Returns all active vehicle types sorted by display order.

curl https://api.dodo.co.tz/api/v1/vehicle-types

Response (200 OK):

[
{
"id": 1,
"name": "Motorcycle",
"slug": "motorcycle",
"description": "Fast delivery for small packages",
"icon": "motorcycle",
"base_fee": 1500,
"price_per_km": 500,
"minimum_fee": 1500,
"max_weight_kg": 20,
"max_distance_km": null,
"is_active": true,
"sort_order": 1
},
{
"id": 2,
"name": "Bicycle",
"slug": "bicycle",
"base_fee": 500,
"price_per_km": 200,
"minimum_fee": 500,
"max_weight_kg": 10,
...
}
]
FieldTypeDescription
idintegerUse this as vehicle_type_id to override auto-selection
namestringDisplay name
slugstringURL-friendly identifier (motorcycle, bicycle, car, van, truck, guta, fuso)
descriptionstringShort marketing description
iconstringIcon identifier for your UI
base_feeintegerFixed base fee in TZS
price_per_kmintegerPer-kilometer rate in TZS
minimum_feeintegerMinimum delivery fee in TZS
max_weight_kgnumber | nullMaximum package weight (null = no limit)
max_distance_kmnumber | nullMaximum delivery distance (null = no limit)
is_activebooleanCurrently available — false types are hidden from this list
sort_orderintegerDisplay order

Indicative same-city pricing

These rates are subject to change — always fetch live from GET /vehicle-types. Regional shipments use a different weight-based transit model (see Rate Quotes).

VehicleBasePer kmMinUse case
Bicycle500200500Documents, very small items
On Foot500150500Hyperlocal (< 1 km)
Motorcycle1,5005001,500Food, small parcels (default)
Guta (Bajaj)2,0007002,000Multiple containers, medium loads
Car3,0001,0003,000Larger packages
Van5,0002,0005,000Bulk, furniture
Truck10,0005,00010,000Heavy cargo
FusovariesvariesvariesLarge freight

Pricing formula (same-city)

fee = max(base_fee + distance_km × price_per_km, minimum_fee)

Example: 5.2 km motorcycle delivery →

fee = max(1500 + 5.2 × 500, 1500)
fee = max(4100, 1500)
fee = 4100 TZS

A 10% service fee is added on top. See Rate Quotes for the full pricing breakdown including fragile surcharges.

Letting your customer choose

If you want to surface vehicle options in your UI:

// Fetch once at app load — cache in memory
const vehicles = await fetch(
'https://api.dodo.co.tz/api/v1/vehicle-types'
).then(r => r.json());

// Filter to whatever you want to offer
const choices = vehicles.filter(v => ['bicycle', 'motorcycle', 'car'].includes(v.slug));

// For each option, get a quote
for (const v of choices) {
const quote = await fetch('/api/v1/merchant/shipments/rates', {
method: 'POST',
headers: {
'X-API-Key': API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
pickup_latitude: -6.79,
pickup_longitude: 39.21,
dropoff_latitude: -6.76,
dropoff_longitude: 39.24,
items: [{ description: 'parcel', weight_kg: 2, quantity: 1 }],
vehicle_type_id: v.id, // force this vehicle
}),
}).then(r => r.json());

console.log(`${v.name}: ${quote.total_amount} TZS`);
}

Caching

Vehicle types change rarely. Cache GET /vehicle-types results for at least an hour on your side — don't fetch them on every quote request.