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_DAYorNEXT_DAY. This is what drives the fee, together with the distance. - packageSize —
SMALL,MEDIUM, orLARGE. 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
2xxquickly 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.