Developers
API reference
Ten keyed endpoints cover the whole lifecycle, plus one open read for tracking. Bodies are JSON in and JSON out, times are ISO-8601 in UTC, money is in Ghana cedis and every id is a UUID.
- POST
/partner/quotesPrice a pickup → drop-off pair.
Costs nothing and commits to nothing. Uses the same rate card as the app: a base fare, a distance fee over the real road distance, and a flat service fee.
- POST
/partner/deliveriesBook a delivery.
Priced and dispatched server-side. Returns immediately with status `pending`; a rider is found within seconds and you are told about it by webhook.
- GET
/partner/deliveriesList your deliveries, newest first.
Paginated with `limit` (default 20, max 100) and `offset`.
- GET
/partner/deliveries/{id}Fetch one delivery.
Includes the full event trail — every status change with its timestamp.
- POST
/partner/deliveries/{id}/cancelCancel a delivery.
Allowed until the rider has picked the parcel up. Free before a rider accepts.
- GET
/partner/payment-methodsList the Mobile Money accounts you can be debited from.
Default first. Never returns anything provider-internal.
- POST
/partner/payment-methodsRegister a Mobile Money account to pay for deliveries.
The first account you register becomes the default automatically.
- DELETE
/partner/payment-methods/{id}Remove a Mobile Money account.
Stops future bookings debiting it. Deliveries already booked against it, and money already taken, are unaffected.
- GET
/partner/webhookRead your configured callback URL.
- PUT
/partner/webhookSet, replace or clear your callback URL.
Setting a URL returns a fresh signing secret and invalidates the previous one. Send `null` to stop receiving callbacks.
- GET
/track/{id}/publicRedacted progress, route and the rider's live position. No key.
Outside /partner deliberately: the delivery id is the credential, so this is the one endpoint you can call from your customer's browser. See Tracking.
Quote a route
Optional — booking prices the delivery itself. Use it to show a customer a fare before they commit.
POST https://api.okadadrop.com/api/v1/partner/quotes
{
"pickup_lat": 5.5560,
"pickup_lng": -0.1820,
"dropoff_lat": 5.6360,
"dropoff_lng": -0.1530
}{
"base_fare": 10.0,
"distance_km": 7.4,
"distance_fee": 14.36,
"service_fee": 2.0,
"total_fare": 26.36
}Book a delivery
The one call that matters. OkadaDrop prices the route, charges it to the payment method you name, and starts looking for a rider immediately.
curl -X POST https://api.okadadrop.com/api/v1/partner/deliveries \
-H "Authorization: Bearer $SOMA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"pickup_address": "Osu, Accra",
"pickup_lat": 5.5560, "pickup_lng": -0.1820,
"dropoff_address": "East Legon, Accra",
"dropoff_lat": 5.6360, "dropoff_lng": -0.1530,
"package_size": "small",
"package_description": "Two shirts, boxed",
"recipient_name": "Ama",
"recipient_phone": "+233209876543",
"payment_method": "paystack",
"verification_method": "otp"
}'{
"id": "8f3b2c1e-...",
"status": "pending",
"pickup_address": "Osu, Accra",
"dropoff_address": "East Legon, Accra",
"package_size": "small",
"recipient_name": "Ama",
"recipient_phone": "+233209876543",
"rider_id": null,
"base_fare": 10.0,
"distance_km": 7.4,
"distance_fee": 14.36,
"service_fee": 2.0,
"total_fare": 26.36,
"payment_method": "paystack",
"payment_status": "pending",
"verification_method": "otp",
"created_at": "2026-07-30T10:14:03Z",
"events": [
{ "status": "pending", "created_at": "2026-07-30T10:14:03Z" }
]
}Request fields
| Field | Type | Notes |
|---|---|---|
| pickup_address | Typestring | NotesHuman-readable, shown to the rider. |
| pickup_lat / pickup_lng | Typenumber | NotesWhere the rider collects. Accuracy here is what makes a pickup quick. |
| dropoff_address | Typestring | NotesHuman-readable, shown to the rider. |
| dropoff_lat / dropoff_lng | Typenumber | NotesWhere it is going. |
| package_size | Typesmall | medium | large | NotesWhat a bike can carry: an envelope, a backpack, a boot-sized box. |
| recipient_name | Typestring | NotesWho the rider is handing it to. |
| recipient_phone | Typestring | NotesGhana format, e.g. +233209876543. Used for masked calling, never shared in the clear. |
| payment_method | Typepaystack | NotesThe only value. Deliveries are prepaid by Mobile Money — riders carry no cash. See Paying for deliveries. |
| payment_method_id | TypeUUID · optional | NotesWhich registered MoMo account to debit. Omit and your default is used. |
| package_description | Typestring · optional | NotesA note for the rider — 'fragile', 'ask for Ama at reception'. |
| verification_method | Typenone | otp | photo · optional | NotesProof of handover. `otp` texts the recipient a code the rider must enter; `photo` requires an uploaded picture. |
| scheduled_at | TypeISO-8601 · optional | NotesBook ahead. Omit to dispatch now. |
| additional_stops | Typearray · optional | NotesUp to 10 earlier drop-offs, visited in the order given before the address above. One rider carries the lot. |
Turning an address into coordinates
Booking takes latitude and longitude, so text addresses have to be resolved first. Two open endpoints do it — no API key, and they are the same ones the OkadaDrop app searches with, so they know Ghanaian addresses that a general geocoder does not.
- GET
/places/autocomplete?q=Search addresses, returning suggestions with a place_id each.
- GET
/places/resolve?place_id=Turn one suggestion into the coordinates you book with.
# 1. Search. No key needed on either of these.
curl "https://api.okadadrop.com/api/v1/places/autocomplete?q=A%26C%20Mall"
# [{ "place_id": "ChIJ…", "title": "A&C Mall", "subtitle": "East Legon, Accra" }]
# 2. Turn the one you want into coordinates.
curl "https://api.okadadrop.com/api/v1/places/resolve?place_id=ChIJ…"
# { "address": "A&C Mall, East Legon, Accra", "lat": 5.6360, "lng": -0.1530 }Resolve once, when the customer picks the address — not on every booking. A stored coordinate is faster, cheaper and cannot drift on you between the order and the dispatch.
Multiple drop-offs
One rider can carry up to ten earlier stops before the destination in the fields above. Send them in additional_stops, in the order you want them visited — each is a drop in its own right, with its own recipient, phone and parcel. The fare is priced over the whole route, and the delivery only reaches delivered once the last stop is handed over.
{
"pickup_address": "Osu, Accra",
"pickup_lat": 5.5560, "pickup_lng": -0.1820,
"additional_stops": [
{
"dropoff_address": "Airport Residential, Accra",
"dropoff_lat": 5.6050, "dropoff_lng": -0.1760,
"recipient_name": "Kofi",
"recipient_phone": "+233201234567",
"package_size": "small"
}
],
"dropoff_address": "East Legon, Accra",
"dropoff_lat": 5.6360, "dropoff_lng": -0.1530,
"package_size": "small",
"recipient_name": "Ama",
"recipient_phone": "+233209876543",
"payment_method": "paystack"
}| Field | Type | Notes |
|---|---|---|
| dropoff_address | Typestring | NotesHuman-readable, shown to the rider. |
| dropoff_lat / dropoff_lng | Typenumber | NotesWhere this stop is. |
| recipient_name | Typestring | NotesWho takes the parcel at this stop. |
| recipient_phone | Typestring | NotesGhana format. Each stop is called on its own number. |
| package_size | Typesmall | medium | large | NotesWhat this stop's parcel is. |
| package_description | Typestring · optional | NotesA note for the rider about this stop. |
Track a delivery
Fetch one by id for its current status and full event trail, or list the account’s deliveries newest-first. Both are the same records the dashboard shows.
curl "https://api.okadadrop.com/api/v1/partner/deliveries?limit=20&offset=0" \
-H "Authorization: Bearer $SOMA_API_KEY"Statuses
A delivery moves forward only. Three of these are terminal — nothing follows them.
| Status | Meaning |
|---|---|
| pending | MeaningBooked. Looking for a rider. |
| accepted | MeaningA rider took the job and is riding to your pickup. |
| picked_up | MeaningThe parcel is on the bike. |
| in_transit | MeaningOn the way to the drop-off. |
| delivered | MeaningHanded over. Terminal. |
| cancelled | MeaningCalled off by you or by the sender. Terminal. |
| expired | MeaningNo rider could be found before the deadline. Terminal — book again. |
Cancel a delivery
POST /partner/deliveries/{id}/cancel works until the rider has the parcel in hand. Before a rider accepts it costs nothing. Afterwards you are cancelling a rider already on their way, so do it quickly or not at all. Past picked_up the call returns a 400: the parcel is on a bike across town, and the way to deal with that is to call the rider from the delivery record.
Pagination
List endpoints take limit (default 20, max 100) and offset. They return a plain JSON array — there is no envelope and no cursor. Page until you get back fewer rows than you asked for.