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:
- The engine sums the chargeable weight across all items.
- It picks the cheapest active vehicle whose
max_weight_kgcovers the total. - The chosen
vehicle_idandvehicle_namecome 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,
...
}
]
| Field | Type | Description |
|---|---|---|
id | integer | Use this as vehicle_type_id to override auto-selection |
name | string | Display name |
slug | string | URL-friendly identifier (motorcycle, bicycle, car, van, truck, guta, fuso) |
description | string | Short marketing description |
icon | string | Icon identifier for your UI |
base_fee | integer | Fixed base fee in TZS |
price_per_km | integer | Per-kilometer rate in TZS |
minimum_fee | integer | Minimum delivery fee in TZS |
max_weight_kg | number | null | Maximum package weight (null = no limit) |
max_distance_km | number | null | Maximum delivery distance (null = no limit) |
is_active | boolean | Currently available — false types are hidden from this list |
sort_order | integer | Display 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).
| Vehicle | Base | Per km | Min | Use case |
|---|---|---|---|---|
| Bicycle | 500 | 200 | 500 | Documents, very small items |
| On Foot | 500 | 150 | 500 | Hyperlocal (< 1 km) |
| Motorcycle | 1,500 | 500 | 1,500 | Food, small parcels (default) |
| Guta (Bajaj) | 2,000 | 700 | 2,000 | Multiple containers, medium loads |
| Car | 3,000 | 1,000 | 3,000 | Larger packages |
| Van | 5,000 | 2,000 | 5,000 | Bulk, furniture |
| Truck | 10,000 | 5,000 | 10,000 | Heavy cargo |
| Fuso | varies | varies | varies | Large 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.