🛵
Dodo API

Dodo Delivery API

Integrate delivery services into your app in minutes.

Base URL

https://api.dodo.co.tz/api/v1

Sandbox

https://sandbox.dodo.co.tz/api/v1

Getting Started

1

Create a Merchant Account

Sign up at dashboard.dodo.co.tz or contact sales@dodo.co.tz

2

Get Your API Key

After approval, find your API key in Dashboard → Settings → API Keys

3

Make Your First Request

curl -X POST https://api.dodo.co.tz/api/v1/orders \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "customer_name": "John Doe",
    "customer_phone": "+255712345678",
    "pickup_address": "Your Store, Samora Ave, Dar es Salaam",
    "dropoff_address": "Customer Address, Mikocheni",
    "package_description": "1x Food order"
  }'

Authentication

Include your API key in the Authorization header:

Authorization: Bearer dodo_live_abc123...
Environment Key Prefix
Production dodo_live_
Sandbox dodo_test_

⚠️ Security: Keep your API key secret. Never expose it in client-side code.

Pricing

POST /orders/pricing

Get delivery estimate before creating an order.

Request

{
  "pickup_latitude": -6.7924,
  "pickup_longitude": 39.2083,
  "dropoff_latitude": -6.7800,
  "dropoff_longitude": 39.2200
}

Response

{
  "distance_km": 5.2,
  "delivery_fee": 2600,
  "currency": "TZS",
  "estimated_time_minutes": 25
}

Pricing Structure

Per kilometer 500 TZS
Minimum charge 500 TZS
Maximum distance 50 km

Orders

POST /orders

Create a new delivery order.

Request

{
  "customer_name": "Jane Doe",
  "customer_phone": "+255765432109",
  "customer_email": "jane@email.com",
  "pickup_address": "Malaika Restaurant, Samora Ave",
  "pickup_latitude": -6.7924,
  "pickup_longitude": 39.2083,
  "pickup_instructions": "Ask for manager",
  "dropoff_address": "Apartment 4B, Ocean View, Msasani",
  "dropoff_latitude": -6.7600,
  "dropoff_longitude": 39.2400,
  "dropoff_instructions": "Call on arrival",
  "package_description": "Food order - 2 containers",
  "package_size": "medium"
}

Parameters

Field Type Required Description
customer_name string Yes Recipient's name
customer_phone string Yes Recipient's phone (+255...)
pickup_address string Yes Pickup location
dropoff_address string Yes Delivery location
package_description string Yes What's being delivered
pickup_latitude number No GPS coordinate
pickup_longitude number No GPS coordinate
package_size string No small, medium, large
metadata object No Your custom data

Response

{
  "id": "ord_abc123",
  "order_number": "ORD-XK7M2P",
  "status": "pending",
  "customer_name": "Jane Doe",
  "distance_km": 8.5,
  "delivery_fee": 4250,
  "currency": "TZS",
  "estimated_time_minutes": 35,
  "created_at": "2024-01-15T12:00:00Z",
  "tracking_url": "https://track.dodo.co.tz/ORD-XK7M2P"
}

💡 Tip: Providing GPS coordinates improves accuracy and speeds up delivery.

GET /orders

List all your orders with pagination and filters.

Query Parameters

page Page number (default: 1)
size Results per page (default: 20, max: 100)
status Filter by status
date_from From date (YYYY-MM-DD)
date_to To date (YYYY-MM-DD)

Example

GET /orders?status=delivered&date_from=2024-01-01&size=50
GET /orders/{order_id}

Get detailed information about an order including rider location.

Response

{
  "id": "ord_abc123",
  "order_number": "ORD-XK7M2P",
  "status": "in_transit",
  "rider": {
    "name": "James M.",
    "phone": "+255787654321",
    "location": {
      "latitude": -6.7700,
      "longitude": 39.2300,
      "updated_at": "2024-01-15T12:35:00Z"
    }
  },
  "estimated_delivery": "2024-01-15T12:45:00Z",
  "tracking_url": "https://track.dodo.co.tz/ORD-XK7M2P"
}
POST /orders/{order_id}/cancel

Cancel an order before it's picked up.

Request

{
  "reason": "Customer requested cancellation"
}

Response

{
  "id": "ord_abc123",
  "status": "cancelled",
  "refund_status": "processing"
}

⚠️ Orders can only be cancelled before pickup. Once picked up, contact support.

Order Status

Status Description
pending Order created, awaiting payment
confirmed Payment confirmed, finding rider
assigned Rider assigned, heading to pickup
picked_up Package collected
in_transit On the way to customer
delivered Successfully delivered ✓
cancelled Order cancelled
failed Delivery failed

Status Flow

pending confirmed assigned picked_up in_transit delivered

Webhooks

Get real-time updates when order status changes.

Setup

  1. Go to Dashboard → Settings → Webhooks
  2. Add your endpoint URL (must be HTTPS)
  3. Select events to receive
  4. Save your webhook secret

Events

order.confirmed Payment confirmed
order.assigned Rider assigned
order.picked_up Package picked up
order.in_transit On the way
order.delivered Delivered
order.cancelled Cancelled
order.failed Failed

Webhook Payload

{
  "event": "order.delivered",
  "timestamp": "2024-01-15T12:45:00Z",
  "data": {
    "id": "ord_abc123",
    "order_number": "ORD-XK7M2P",
    "status": "delivered",
    "customer_name": "Jane Doe",
    "delivery_fee": 4250,
    "rider": {
      "name": "James M.",
      "phone": "+255787654321"
    },
    "delivered_at": "2024-01-15T12:45:00Z",
    "metadata": {
      "your_order_id": "12345"
    }
  }
}

Verify Signature

Always verify webhook signatures to ensure authenticity.

Signature header:

X-Dodo-Signature: sha256=a1b2c3d4...
const crypto = require('crypto');

function verifyWebhook(payload, signature, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');
  
  return `sha256=${expected}` === signature;
}

app.post('/webhooks/dodo', (req, res) => {
  const signature = req.headers['x-dodo-signature'];
  
  if (!verifyWebhook(JSON.stringify(req.body), signature, SECRET)) {
    return res.status(401).send('Invalid');
  }
  
  // Process webhook...
  res.status(200).send('OK');
});
import hmac, hashlib
from flask import Flask, request

app = Flask(__name__)

@app.route('/webhooks/dodo', methods=['POST'])
def webhook():
    signature = request.headers.get('X-Dodo-Signature', '')
    payload = request.get_data()
    
    expected = 'sha256=' + hmac.new(
        SECRET.encode(),
        payload,
        hashlib.sha256
    ).hexdigest()
    
    if not hmac.compare_digest(expected, signature):
        return 'Invalid', 401
    
    # Process webhook...
    return 'OK', 200
<?php
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_DODO_SIGNATURE'];
$secret = getenv('DODO_WEBHOOK_SECRET');

$expected = 'sha256=' . hash_hmac('sha256', $payload, $secret);

if (!hash_equals($expected, $signature)) {
    http_response_code(401);
    exit('Invalid signature');
}

$data = json_decode($payload, true);
// Process webhook...

Errors

Error Response Format

{
  "error": {
    "code": "invalid_request",
    "message": "Customer phone number is required"
  }
}
Code HTTP Description
invalid_request 400 Missing or invalid parameters
unauthorized 401 Invalid or missing API key
forbidden 403 Account not approved
not_found 404 Order not found
rate_limit_exceeded 429 Too many requests
internal_error 500 Server error

Code Examples

const axios = require('axios');

const dodo = axios.create({
  baseURL: 'https://api.dodo.co.tz/api/v1',
  headers: {
    'Authorization': `Bearer ${process.env.DODO_API_KEY}`,
    'Content-Type': 'application/json'
  }
});

async function createDelivery(orderData) {
  const response = await dodo.post('/orders', {
    customer_name: orderData.customerName,
    customer_phone: orderData.customerPhone,
    pickup_address: 'Your Store Address',
    dropoff_address: orderData.deliveryAddress,
    package_description: `Order #${orderData.orderId}`
  });
  
  return response.data;
}

// Usage
const delivery = await createDelivery({
  customerName: 'John Doe',
  customerPhone: '+255712345678',
  deliveryAddress: '123 Street, Dar es Salaam',
  orderId: '12345'
});

console.log(`Tracking: ${delivery.tracking_url}`);
import requests
import os

class DodoClient:
    def __init__(self):
        self.base_url = 'https://api.dodo.co.tz/api/v1'
        self.api_key = os.environ['DODO_API_KEY']
        self.session = requests.Session()
        self.session.headers.update({
            'Authorization': f'Bearer {self.api_key}',
            'Content-Type': 'application/json'
        })
    
    def create_order(self, customer_name, customer_phone, 
                     dropoff_address, package_description):
        response = self.session.post(
            f'{self.base_url}/orders',
            json={
                'customer_name': customer_name,
                'customer_phone': customer_phone,
                'pickup_address': 'Your Store Address',
                'dropoff_address': dropoff_address,
                'package_description': package_description
            }
        )
        response.raise_for_status()
        return response.json()

# Usage
dodo = DodoClient()
order = dodo.create_order(
    customer_name='John Doe',
    customer_phone='+255712345678',
    dropoff_address='123 Street, Dar es Salaam',
    package_description='Food delivery'
)
print(f"Tracking: {order['tracking_url']}")
<?php

class DodoClient {
    private $baseUrl = 'https://api.dodo.co.tz/api/v1';
    private $apiKey;
    
    public function __construct($apiKey) {
        $this->apiKey = $apiKey;
    }
    
    public function createOrder($data) {
        $ch = curl_init();
        
        curl_setopt_array($ch, [
            CURLOPT_URL => $this->baseUrl . '/orders',
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => json_encode($data),
            CURLOPT_HTTPHEADER => [
                'Authorization: Bearer ' . $this->apiKey,
                'Content-Type: application/json'
            ]
        ]);
        
        $response = curl_exec($ch);
        curl_close($ch);
        
        return json_decode($response, true);
    }
}

// Usage
$dodo = new DodoClient(getenv('DODO_API_KEY'));

$order = $dodo->createOrder([
    'customer_name' => 'John Doe',
    'customer_phone' => '+255712345678',
    'pickup_address' => 'Your Store Address',
    'dropoff_address' => '123 Street, Dar es Salaam',
    'package_description' => 'Food delivery'
]);

echo "Tracking: " . $order['tracking_url'];

Testing

Sandbox Environment

Use sandbox for testing without real payments:

Base URL: https://sandbox.dodo.co.tz/api/v1
API Key:  dodo_test_...

Test Phone Numbers

+255700000001 Payment succeeds
+255700000002 Payment fails
+255700000003 Payment pending (timeout)

💡 Auto-progression: In sandbox, orders automatically progress through statuses for testing.

Support

📧

Email Support

api-support@dodo.co.tz
💬

Dashboard Support

dashboard.dodo.co.tz/support
📊

System Status

status.dodo.co.tz
📚

Documentation

docs.dodo.co.tz