How it works Ways to book Pricing For riders Developers Get started

Wire delivery into what you already run

A small REST surface over HTTPS, JSON in and out, one API key per merchant. Create a delivery, subscribe to its lifecycle, and let Kova handle the dispatch.

Base URL and auth

https://api.kovadelivery.com/api/v1

Every request carries your key in an X-API-Key header. A key is scoped to one merchant, so a key can only ever read or write that merchant's deliveries — there is no tenant ID to pass and no way to reach someone else's data by changing one.

curl https://api.kovadelivery.com/api/v1/deliveries \
  -H "X-API-Key: $KOVA_API_KEY"

Keep the key server-side. It is a bearer credential for your whole delivery account. Requests are rate limited per key, so batch work should expect to be throttled rather than assume unlimited throughput.

Creating a delivery

Pickup and drop-off each need an address and coordinates. The recipient needs a name and a phone number — that is who the rider is looking for at the other end.

POST /deliveries

{
  "pickup": {
    "address": "QuickBites, East Legon, Accra",
    "lat": 5.6363,
    "lng": -0.1602
  },
  "dropoff": {
    "address": "Madina Market, Accra",
    "lat": 5.6836,
    "lng": -0.1669
  },
  "recipient": {
    "name": "John Doe",
    "phone": "0240000000"
  },
  "packageSize": "MEDIUM",
  "deliveryType": "SAME_DAY",
  "merchantRef": "order-8842",
  "notes": "Call on arrival"
}

Fields worth knowing

  • deliveryType — SAME_DAY or NEXT_DAY. This is what drives the fee, together with the distance.
  • packageSize — SMALL, MEDIUM, or LARGE. It tells the rider what to expect; it does not change the price.
  • merchantRef — your own order ID, up to 100 characters. Carried through so you can reconcile without keeping a mapping table.
  • notes — up to 500 characters, shown to the rider.

Endpoints

Method Path What it does
POST /deliveries Create a delivery. Returns 201 with the delivery and its ID.
GET /deliveries List your deliveries, paginated — 20 per page by default.
GET /deliveries/{id} The current state of one delivery.
GET /deliveries/{id}/detail The same, plus the full status history.
DELETE /deliveries/{id} Cancel it. Valid while PENDING or ASSIGNED — not once picked up.
GET /deliveries/export CSV export, optionally filtered by status and date range.
POST /merchant/deliveries/parse Turn free text into a reviewable draft. Creates nothing.
GET /merchant/webhooks Your webhook events, filterable by PENDING, DELIVERED, or FAILED.
POST /merchant/webhooks/{id}/retry Retry a FAILED event by hand.

The delivery lifecycle

A delivery is always in exactly one of these states.

Status Meaning
PENDING Priced and offered to nearby riders.
ASSIGNED A rider accepted and is heading to pickup.
PICKED_UP The package is in the rider’s hands.
IN_TRANSIT On the way to the recipient.
DELIVERED Handed to the person receiving it.
CONFIRMED Closed out and settled.
UNASSIGNED No rider accepted in time. Our team is alerted and reassigns it by hand.
CANCELLED You cancelled it, which you can do any time before pickup.
FAILED Something went wrong on the run. The reason is recorded and our team follows up.

Handle UNASSIGNED explicitly. It is not a failure — the delivery is still live and our team is placing it by hand — but it is the signal that it is taking longer than usual, and worth surfacing to whoever is waiting.

Webhooks

Register a URL on your account and Kova posts each status change to it as it happens, rather than you polling. Events you can inspect and replay: list them at GET /merchant/webhooks, filter by PENDING, DELIVERED, or FAILED, and retry a failed one at POST /merchant/webhooks/{id}/retry.

Delivering webhooks reliably

  • Respond 2xx quickly and do your work afterwards. A slow endpoint gets treated as a failed one.
  • Make your handler idempotent. A retried event will arrive more than once, and you want the second copy to be harmless.
  • Don't assume ordering. Compare against the status you have stored rather than trusting arrival order.
  • A failed event is retried automatically and then left for you to replay, so a deploy window doesn't cost you the events.

Drafting from plain text

POST /merchant/deliveries/parse takes a sentence and returns a structured draft for a human to confirm. It deliberately creates nothing — you show the draft, the merchant checks it, and you create it through POST /deliveries as normal.

POST /merchant/deliveries/parse

{ "text": "Pizza from QuickBites East Legon to John on 0240000000 in Madina" }

This endpoint depends on a model credential configured on the deployment. If it isn't set you'll get a 503 — treat it as optional and always keep the ordinary form available as the fallback.

WhatsApp and Telegram

Your merchants can also create deliveries by message, and those arrive as ordinary deliveries on your account — same statuses, same webhooks. Numbers are registered and verified first, so only confirmed numbers can book.

Path What it does
POST /merchant/whatsapp/numbers Register a number. A six-digit code is sent to it immediately.
GET /merchant/whatsapp/numbers List registered numbers and whether each is verified.
DELETE /merchant/whatsapp/numbers/{id} Remove a number.
POST /merchant/chat-integrations/telegram/link Create a one-time link that pairs a Telegram account.
GET /merchant/chat-integrations See which chat channels are connected.

Getting help

Email bernard@kovaonline.com with the tracking ID or the request you are stuck on and you will get an engineer, not a script.