Skip to main content

Manage Shipments

Once a shipment is created, use these endpoints to list, inspect, and cancel.

List shipments​

GET /merchant/shipments​

Returns your shipments, most recent first.

curl "https://api.dodo.co.tz/api/v1/merchant/shipments?status=delivered&page=1&per_page=20" \
-H "X-API-Key: $DODO_API_KEY"

Query parameters:

ParamTypeDescription
statusstringFilter by shipment status — pending, confirmed, pickup_dispatched, picked_up, in_transit, out_for_delivery, delivered, cancelled, failed
typestringsame_city or regional
searchstringMatch against shipment_number, tracking_code, or merchant_reference
pageintegerPage number (default: 1)
per_pageintegerPage size (default: 20, max: 100)

Response (200 OK):

[
{
"id": 12345,
"shipment_number": "SHP-20260515-A1B2C3",
"tracking_code": "DODO7K9M2P4QR8",
"merchant_reference": "your-internal-order-id-123",
"type": "same_city",
"status": "delivered",
"sender_name": "Malaika Restaurant",
"receiver_name": "Jane Doe",
"pickup_address": "Samora Ave, Dar es Salaam",
"dropoff_address": "Apartment 4B, Msasani, Dar es Salaam",
"total_amount": 4100,
"payment_status": "paid",
"estimated_delivery_at": "2026-05-15T11:30:00Z",
"created_at": "2026-05-15T10:30:00Z"
}
]

The list endpoint returns the trimmed list-view object — call Get details for items, legs, and the event log.

Get shipment details​

GET /merchant/shipments/{id}​

Full shipment record including items, journey legs, and the complete event log.

curl https://api.dodo.co.tz/api/v1/merchant/shipments/12345 \
-H "X-API-Key: $DODO_API_KEY"

The response shape matches the POST /merchant/shipments response, but with up-to-date status and a populated events array.

Event log​

events is the timeline of what happened to this shipment. Use it to display delivery progress in your UI when you can't easily wire up webhooks.

"events": [
{
"id": 102,
"status": "delivered",
"leg_type": "first_mile",
"description": "Package delivered to Jane Doe",
"location_address": null,
"actor_type": "rider",
"occurred_at": "2026-05-15T11:28:14Z"
},
{
"id": 101,
"status": "out_for_delivery",
"leg_type": "first_mile",
"description": "Rider James M. heading to receiver",
"actor_type": "rider",
"occurred_at": "2026-05-15T11:05:42Z"
},
{
"id": 100,
"status": "picked_up",
"leg_type": "first_mile",
"description": "Package collected from Malaika Restaurant",
"actor_type": "rider",
"occurred_at": "2026-05-15T10:48:09Z"
}
]

Events are ordered newest first.

Cancel a shipment​

POST /merchant/shipments/{id}/cancel​

Cancellation is state-graded — what you pay depends on what the rider has already done. The endpoint is a two-step preview / commit flow so you can surface the fee to your user before charging the wallet.

Fee table​

WindowTriggerFee
grace< 60 s since creation, no rider has accepted yet0 TZS — full refund
assignedRider has accepted but isn't moving yet30% of first_mile_fee
en_routeRider is moving toward pickup (or has arrived)100% of first_mile_fee
forbiddenRider has the package (picked_up and beyond)Cannot cancel — create a return-to-sender shipment instead

The fee is retained; the remainder is credited back to your wallet — regardless of whether the original payment came from wallet or gateway. Gateway-paid shipments do not trigger a Selcom reverse charge; the refund lands as wallet balance you can spend on your next shipment.

Step 1 — Preview the fee​

Call without confirm=true to get a quote. No state change.

curl -X POST https://api.dodo.co.tz/api/v1/merchant/shipments/12345/cancel \
-H "X-API-Key: $DODO_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "reason": "Customer changed their mind" }'

Response (200):

{
"status": "cancellation_pending",
"shipment_id": 12345,
"shipment_number": "SHP-20260515-A1B2C3",
"window": "assigned",
"fee": 800,
"refund": 3300,
"currency": "TZS",
"reason_for_fee": "Rider has accepted but not yet started moving (30% of first-mile fee retained)."
}

Surface reason_for_fee to your end user — "Cancel this order? Your wallet will be charged 800 TZS because a rider has been dispatched."

Step 2 — Commit​

Re-send the same request with ?confirm=true to commit:

curl -X POST "https://api.dodo.co.tz/api/v1/merchant/shipments/12345/cancel?confirm=true" \
-H "X-API-Key: $DODO_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "reason": "Customer changed their mind" }'

Response (200):

{
"status": "cancelled",
"shipment_id": 12345,
"shipment_number": "SHP-20260515-A1B2C3",
"window": "assigned",
"fee_debited": 800,
"refund_credited": 3300,
"currency": "TZS",
"shipment": { /* full shipment record */ }
}

The fee is recomputed at commit time. If the shipment has advanced a window between your preview and your commit (e.g. the rider started moving), the new (higher) fee applies — re-preview to see what your user is now agreeing to.

Request body fields:

FieldTypeRequiredDescription
reasonstringyesWhy you're cancelling (5–500 chars) — appears in admin reports

Query parameters:

ParamTypeDefaultDescription
confirmbooleanfalseSet to true to commit; otherwise returns a fee preview
Past the pickup window

Once a rider has the package (picked_up and beyond), cancellation is forbidden. The API returns:

{ "detail": "Rider already has the package. Create a return-to-sender shipment instead of cancelling." }

What happens on commit​

  1. Cancellation fee retained as platform revenue (if fee > 0)
  2. Remainder credited back to wallet as a REFUND transaction — works the same for wallet-paid and gateway-paid shipments; gateway-paid funds become wallet balance rather than going back through Selcom
  3. Shipment status flips to cancelled
  4. Webhook shipment.cancelled fires with { window, fee, refund, reason }
  5. If a rider was dispatched, they're notified the job is cancelled
  • Webhooks — get status updates pushed to your server instead of polling
  • Shipment Lifecycle — full state machine including which transitions can cancel
  • Errors — error codes and retry strategy