Skip to main content

Create a Shipment

Create and immediately pay for a delivery in a single call. Dodo debits your wallet, dispatches a rider, and starts firing webhooks within seconds.

POST /merchant/shipments

curl -X POST https://api.dodo.co.tz/api/v1/merchant/shipments \
-H "X-API-Key: $DODO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"sender_name": "Malaika Restaurant",
"sender_phone": "+255712345678",
"pickup_address": "Samora Ave, Dar es Salaam",
"pickup_latitude": -6.7924,
"pickup_longitude": 39.2083,
"pickup_notes": "Ask for the manager at the counter",
"receiver_name": "Jane Doe",
"receiver_phone": "+255765432109",
"dropoff_address": "Apartment 4B, Msasani, Dar es Salaam",
"dropoff_latitude": -6.7600,
"dropoff_longitude": 39.2400,
"dropoff_notes": "Call on arrival — gate code 1234",
"items": [
{ "description": "Food order — 2 containers", "weight_kg": 2.5, "quantity": 1 }
],
"payment_source": "wallet",
"merchant_reference": "your-internal-order-id-123"
}'

Request

Sender (your pickup point)

FieldTypeRequiredDescription
sender_namestringyesYour business or contact name
sender_phonestringyesE.164 format: +255712345678
sender_emailstringnoEmail for delivery receipts

Pickup location

FieldTypeRequiredDescription
pickup_addressstringyesHuman-readable pickup address (5–500 chars)
pickup_latitudenumbernoStrongly recommended for accurate dispatch
pickup_longitudenumbernoRequired if pickup_latitude is sent
pickup_notesstringnoFree-text instructions for the rider (≤ 500 chars)

Receiver (your end customer)

FieldTypeRequiredDescription
receiver_namestringyesFull name (2–255 chars)
receiver_phonestringyesE.164 — receives the tracking-link SMS
receiver_emailstringnoEmail for delivery confirmation

Dropoff location

FieldTypeRequiredDescription
dropoff_addressstringyesHuman-readable dropoff address
dropoff_latitudenumbernoStrongly recommended
dropoff_longitudenumbernoRequired if dropoff_latitude is sent
dropoff_notesstringnoFree-text instructions for the rider

Items

items is an array of 1–50 items. Same fields as the rate quote:

"items": [
{
"description": "Glass decorations",
"weight_kg": 1.2,
"quantity": 1,
"length_cm": 25,
"width_cm": 25,
"height_cm": 30,
"is_fragile": true,
"declared_value": 50000
}
]

Payment & metadata

FieldTypeRequiredDescription
payment_sourcestringyesMust be "wallet" for S2S integrations
vehicle_type_idintegernoForce a vehicle — otherwise auto-selected by weight
merchant_referencestringnoYour internal order ID — stored on the shipment and echoed in every webhook
Always send payment_source: "wallet"

If omitted, the field defaults to "gateway" — which means Dodo creates the shipment in pending status and waits for a Selcom payment URL flow. That path is for cases where your end customer pays Dodo directly. For server-to-server merchant integrations you almost always want wallet.

Use merchant_reference for correlation

Set this to your own order ID. It comes back in every webhook payload so you can match Dodo events to your records without storing Dodo's internal id.

Response

{
"id": 12345,
"shipment_number": "SHP-20260515-A1B2C3",
"tracking_code": "DODO7K9M2P4QR8",
"merchant_reference": "your-internal-order-id-123",
"type": "same_city",
"status": "confirmed",
"vehicle_type_name": "Motorcycle",

"sender_name": "Malaika Restaurant",
"sender_phone": "+255712345678",
"pickup_address": "Samora Ave, Dar es Salaam",
"pickup_latitude": -6.7924,
"pickup_longitude": 39.2083,
"pickup_notes": "Ask for the manager at the counter",

"receiver_name": "Jane Doe",
"receiver_phone": "+255765432109",
"dropoff_address": "Apartment 4B, Msasani, Dar es Salaam",
"dropoff_latitude": -6.76,
"dropoff_longitude": 39.24,
"dropoff_notes": "Call on arrival — gate code 1234",

"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,

"payment_status": "paid",
"payment_confirmed_at": "2026-05-15T10:30:01Z",
"estimated_delivery_at": "2026-05-15T11:30:00Z",
"created_at": "2026-05-15T10:30:00Z",

"items": [...],
"legs": [...],
"events": [...]
}
FieldTypeDescription
idintegerDodo's internal ID — store for webhook matching
shipment_numberstringHuman reference (SHP-YYYYMMDD-XXXXXX)
tracking_codestringPublic tracking token (DODOXXXXXXXXXX) — share with your customer
statusstring"confirmed" immediately after wallet debit. See Shipment Lifecycle
typestring"same_city" or "regional"
total_amountintegerAmount debited from your wallet (TZS)
payment_statusstring"paid" for wallet payments
payment_confirmed_atstringISO timestamp when payment cleared
estimated_delivery_atstringETA — populated based on distance and vehicle
legsarrayJourney legs (1 for same-city, 3 for regional). See Lifecycle
eventsarrayTracking event log — empty at creation, grows as the shipment moves

What happens next

POST /merchant/shipments (status=pending, transient)


Wallet debit succeeds → status=confirmed → webhook: shipment.created (and shipment.paid)


Rider auto-dispatched → status=pickup_dispatched


Rider collects package → status=picked_up → webhook: delivery.picked_up


Rider heads to receiver → status=out_for_delivery


Delivered → status=delivered → webhook: delivery.delivered

Set up Webhooks to receive each transition on your server.

Sharing tracking with your customer

The recipient automatically gets an SMS with the tracking URL when the shipment is confirmed:

Your delivery is on its way. Track here:
https://dododelivery.co.tz/track?code=DODO7K9M2P4QR8

If you want to surface the same tracking link in your own UI (order confirmation page, email, etc.), use the tracking_code from the response:

https://dododelivery.co.tz/track?code={tracking_code}

See Tracking for details.

Errors

StatusDetailCause
400"Insufficient wallet balance. Required: 4100 TZS, Available: 2500 TZS."Top up your wallet
400"Merchant wallet is not active."Wallet was suspended — contact support
400"Either coordinates or an address must be provided for pickup."Send coords or an address for both endpoints
400"Could not geocode dropoff address: '…'."Geocoding failed — send GPS coordinates instead
401"Invalid or expired API key"Check your X-API-Key header
403"Merchant account not approved"Wait for admin approval
422Validation error — see field-level detailsBad request body