Skip to content
Tumira
How it works For shops For riders Track a parcel Developers Get the app
Get the app

Developers · API v1

Tumira deliveries from your online store

When a customer checks out on your site, book a verified Tumira rider automatically. Your customer gets the rider's details, a live tracking link and their delivery code on WhatsApp, and your system hears about every step.

Get started Keys Quotes Book a delivery Look up Cancel The delivery Webhooks Errors and limits

Get started

  1. Sign your shop up in the Tumira app (get it here) and set your shop's pin.
  2. Ask us for API keys on WhatsApp: a test key to build with and a live key when you're ready.
  3. Build against the test key. Test deliveries never reach riders: they get a "Test Rider" and move through every status in about two minutes.
  4. Swap in the live key. Deliveries are then booked and paid exactly as in the app.

Base URL: https://tumira.co.zw/api/v1. Requests and responses are JSON. Money is in US cents. Times are ISO 8601 (UTC).

curl https://tumira.co.zw/api/v1/me \
  -H "Authorization: Bearer tk_test_…"

Keys

Send your key in the Authorization header: Bearer tk_live_… or Bearer tk_test_…. Keep keys on your server; never put them in a web page or app. If a key leaks, tell us and we'll revoke it at once.

Live keys act as your shop: the same fare limits, cash-on-delivery rules and daily limits for new shops apply as in the app.

Quotes

POST /quotes: what a trip costs, for showing a delivery price at checkout.

curl https://tumira.co.zw/api/v1/quotes \
  -H "Authorization: Bearer tk_test_…" -H "Content-Type: application/json" \
  -d '{ "dropoff": { "address": "12 Lomagundi Rd, Avondale", "lat": -17.7931, "lng": 31.0361 }, "size": "small" }'
{
  "object": "quote", "mode": "test", "currency": "USD", "size": "small",
  "distance_km": 4.4, "suggested_cents": 300, "min_cents": 240, "max_cents": 900,
  "dropoff": { "lat": -17.7931, "lng": 31.0361, "pin": "confirmed" }
}

Send dropoff.lat and dropoff.lng when you have them (from a map pin at checkout). With an address only, Tumira places it itself and marks the pin "estimated"; the customer is then asked on WhatsApp to share their location so the rider finds them. size is "small" (default) or "large".

Book a delivery

POST /deliveries. Send an Idempotency-Key header (for example your order number): retrying with the same key never books twice. Without it, a retry after a timeout can book a second rider.

curl https://tumira.co.zw/api/v1/deliveries \
  -H "Authorization: Bearer tk_test_…" -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-1001" \
  -d '{
    "reference": "WC-1001",
    "customer": { "name": "Rudo Moyo", "phone": "0771234567" },
    "dropoff": { "address": "12 Lomagundi Rd, Avondale", "notes": "Blue gate", "lat": -17.7931, "lng": 31.0361 },
    "size": "small",
    "fee_paid_by": "shop",
    "cash_on_delivery": { "amount_cents": 3500, "pay_to": "rider_cash" }
  }'
customer.name, customer.phoneRequired. The phone is the customer's WhatsApp number (07… or 2637…).
dropoff.address, dropoff.lat, dropoff.lngAddress and/or pin; see Quotes. notes: gate, landmark.
referenceYour own order number; you can look deliveries up by it.
size"small" (default) or "large".
fare_centsOptional. Offer more (or less) than the suggested fare, within the quote's min_cents–max_cents.
fee_paid_by"shop" (default; you pay the rider at pickup) or "customer" (they pay the rider at the door).
cash_on_deliveryOptional. amount_cents to collect; pay_to: "shop_ecocash" (customer pays your EcoCash) or "rider_cash" (rider brings the cash back the same day).
scheduled_forOptional. Book for later, up to 7 days ahead.
noteOptional note for the rider.

Returns 201 with the delivery. Give the rider the pickup_code when they collect (print it on the parcel label if you like).

Look up deliveries

GET /deliveries/{id}: {id} is Tumira's id, the TM- code, or your reference.
GET /deliveries?status=searching&limit=20&before=2026-10-20T10:00:00Z: newest first, up to 100.

Cancel

POST /deliveries/{id}/cancel: before the rider picks the parcel up. Cancelling twice is fine (you get the cancelled delivery back). After pickup you get 409 cannot_cancel: use "Cancel and bring it back" in the app.

The delivery

{
  "id": "6f1c…", "object": "delivery", "mode": "live", "code": "TM-1042",
  "status": "picked_up", "reference": "WC-1001",
  "created_at": "2026-10-20T09:12:03Z", "scheduled_for": null,
  "customer": { "name": "Rudo Moyo", "phone": "263771234567" },
  "dropoff": { "address": "12 Lomagundi Rd, Avondale", "notes": "Blue gate", "lat": -17.7931, "lng": 31.0361, "pin": "confirmed" },
  "size": "small",
  "fare": { "amount_cents": 300, "suggested_cents": 300, "currency": "USD", "paid_by": "shop" },
  "cash_on_delivery": { "amount_cents": 3500, "pay_to": "rider_cash", "collected": false },
  "pickup_code": "4821",
  "rider": { "name": "Tendai M.", "phone": "263771112222", "bike": "Honda ACE 125", "plate": "AFB 2193" },
  "tracking_url": "https://tumira.co.zw/t/…",
  "timestamps": { "assigned_at": "…", "picked_up_at": "…" }
}
searchingLooking for a rider. After 3 hours with none, Tumira cancels it and tells you.
assignedA rider is on the way to collect. rider is filled in.
picked_upThe rider has the parcel (checked with your pickup code).
arrivedThe rider is at the customer.
deliveredHanded over with the customer's delivery code.
returning, returnedThe customer wasn't reached; the parcel is coming back to you.
cancelledCancelled by you, or no rider was found.

Webhooks

Give us an https URL (one for test, one for live) and we POST every status change to it as an event. Answer with any 2xx within 10 seconds. Failed attempts are retried after 1 minute, 5 minutes, 30 minutes, 2, 6 and 12 hours. Events can arrive more than once (use the event id to ignore repeats), and after a retry an older event can arrive after a newer one: compare data.timestamps, or fetch the delivery, before going backwards.

{ "id": "evt_381", "object": "event", "type": "delivery.picked_up", "mode": "live",
  "created_at": "2026-10-20T09:31:44Z", "data": { …the delivery… } }

Check the Tumira-Signature header, t=<unix seconds>,v1=<hex>: v1 is HMAC-SHA256 of t + "." + the raw body with your signing secret. Reject it if it doesn't match or t is more than 5 minutes old.

// Node.js
const crypto = require('crypto');
function fromTumira(rawBody, header, secret) {
  const { t, v1 } = Object.fromEntries(header.split(',').map((p) => p.split('=')));
  const want = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
  return Math.abs(Date.now() / 1000 - Number(t)) < 300 &&
    crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(want));
}
// PHP (WooCommerce)
function from_tumira($raw_body, $header, $secret) {
  parse_str(str_replace(',', '&', $header), $p);
  $want = hash_hmac('sha256', $p['t'] . '.' . $raw_body, $secret);
  return abs(time() - (int) $p['t']) < 300 && hash_equals($want, $p['v1']);
}

Errors and limits

{ "error": { "code": "rejected", "message": "Offer a fare from $2.40 to $9.00 for this trip", "request_id": "req_9c1e…" } }
400Something in the request is missing or malformed (invalid_customer, invalid_dropoff, …).
401 / 403Missing, wrong or revoked key; or the shop isn't active.
404No such delivery for this key's mode.
409Not possible in the delivery's current state (cannot_cancel), or the key needs replacing (shop_not_ready).
422Tumira's rules said no (rejected, outside_area, address_not_found). The message says why, in words you can show your team.
429More than 120 requests a minute for one key.
500Our side. Retry with the same Idempotency-Key; quote the request_id if you contact us.

Questions? WhatsApp +263 71 488 8633 or [email protected].

Test mode notes

Test deliveries use a fixed pickup code (1234), no real tracking page, and are deleted after 30 days. No WhatsApp messages go out.

Tumira

Deliveries for Harare shops, by verified riders.

Talk to us WhatsApp +263 71 488 8633 [email protected]
Tumira Areas we deliver Developers Privacy policy

© Tumira, operated by AIVA Solutions (Pvt) Ltd, Harare.