Overview
The GO Delivery API lets your platform (a shop, marketplace or logistics tool) book parcel deliveries from GO drivers, get a price up front, follow each delivery and receive events when its status changes. Calls are server to server. Your customers never see a GO key and do not need a GO account first.
Base URL: https://api.go.mendifier.com/api/v1. Money is Nigerian naira in whole naira. Distances are in km. Every response is {"success": true, "data": …}, and every error is {"success": false, "error": {"code", "message"}}.
Quickstart
1. Sign up for a test key (it starts with go_test_). 2. Ask for the vehicles your plan includes, 3. get a binding quote, 4. book it.
# 1. what can I book?
curl https://api.go.mendifier.com/api/v1/delivery/vehicle-types \
-H "Authorization: Bearer $GO_KEY"
# 2. a binding quote for every vehicle in my plan
curl -X POST https://api.go.mendifier.com/api/v1/delivery/quotes \
-H "Authorization: Bearer $GO_KEY" -H "Content-Type: application/json" -d '{
"pickup": {"address":"Adeola Odeku St, Victoria Island, Lagos","lat":6.4281,"lng":3.4219},
"dropoff": {"address":"Admiralty Way, Lekki Phase 1, Lagos","lat":6.4474,"lng":3.4723} }'
# 3. book it with the quote_id, at the quoted price
curl -X POST https://api.go.mendifier.com/api/v1/delivery/deliveries \
-H "Authorization: Bearer $GO_KEY" -H "X-GO-User: usr_8321" \
-H "Idempotency-Key: $(uuidgen)" -H "Content-Type: application/json" -d '{
"quote_id": "qte_…", "vehicle_type": "motorcycle", "partner_ref": "ORD-10492",
"pickup": {"address":"Adeola Odeku St, Victoria Island, Lagos","lat":6.4281,"lng":3.4219,"contact_name":"Ngozi","contact_phone":"08031234567"},
"dropoff": {"address":"Admiralty Way, Lekki Phase 1, Lagos","lat":6.4474,"lng":3.4723},
"recipient": {"name":"Chinedu Eze","phone":"08091234567"},
"parcel": {"description":"Two shirts"} }'Authentication
Send your key on every request, either way:
Authorization: Bearer go_live_XXXXXXXXXXXXXXXXXXXXXXXX
# or
x-api-key: go_live_XXXXXXXXXXXXXXXXXXXXXXXX
- Keys do not expire. They are stored hashed and shown once, with a visible prefix (for example
go_live_k3f9…) so you can tell them apart. A key works until you revoke it. - Rotate or revoke any key in the dashboard. Rotating makes a replacement with the same label and stops the old key immediately. Each key shows when it was last used.
- Test keys (
go_test_…) are free: no real drivers, no GO accounts, no billing. Live keys (go_live_…) need an active paid plan, and book real deliveries. - Keep keys on your server. Never put one in a mobile app or a web page.
- Keys issued earlier by GO (
gk_…) keep working, and the OAuth client-credentials flow (POST /v1/oauth/token) is still available.
Vehicle types
GO delivers by motorcycle, car, van and truck, and GO can add more (for example a tricycle). Which of them you can book depends on your plan. Ask the API, and show your customers only what comes back:
curl https://api.go.mendifier.com/api/v1/delivery/vehicle-types -H "Authorization: Bearer $GO_KEY"
{ "success": true, "data": {
"plan": {"code":"growth","name":"Growth"},
"vehicle_types": [
{"vehicle_type":"motorcycle","name":"Motorcycle","icon":"motorcycle","max_weight_kg":20,"volume_hint":"Fits a backpack"},
{"vehicle_type":"car","name":"Car","icon":"car","max_weight_kg":500,"volume_hint":"Fits a car boot"} ],
"not_in_plan": [ {"vehicle_type":"van","name":"Van","required_plan":"business"} ],
"scheduling": {"enabled":true,"min_lead_minutes":15,"max_days_ahead":30,"free_cancel_until_minutes_before":60},
"features": {"webhooks":true,"usage_reports":false,"multiple_keys":true,"max_live_keys":2} } }
icon is a Phosphor icon name. Each vehicle has its own price and estimated arrival time on a quote. Asking for a vehicle your plan does not include returns 403 PLAN_FEATURE_REQUIRED.
Quotes
POST /v1/delivery/quotes returns a quote for every vehicle your plan includes, or for one if you send vehicle_type. Quotes are binding: book with the quote_id within valid_for_seconds (600) and the price you were shown is the price you pay.
{ "object":"quote", "id":"qte_a1b2…", "binding":true, "expires_at":"2026-10-28T10:10:00.000Z", "valid_for_seconds":600,
"vehicle_type":"motorcycle", "price":{"amount":2800,"currency":"NGN"}, "distance_km":9.4, "estimated_duration_min":22,
"scheduled_for":null,
"vehicle_quotes":[
{"vehicle_type":"motorcycle","name":"Motorcycle","icon":"motorcycle","available":true,"quote_id":"qte_a1b2…","price":{"amount":2800,"currency":"NGN"},"estimated_duration_min":22},
{"vehicle_type":"car","name":"Car","icon":"car","available":true,"quote_id":"qte_c3d4…","price":{"amount":4200,"currency":"NGN"},"estimated_duration_min":28} ] }
| Field | Notes |
|---|---|
pickup, dropoff | address, lat, lng. Both must be inside a GO service area. |
vehicle_type | Optional. Quote just this vehicle. |
scheduled_for | Optional ISO time for a later pickup. The price is for that time. |
parcel.size | Optional older way to pick a vehicle: small = motorcycle, medium = car, large = van. |
Book with an expired quote and you get 409 QUOTE_EXPIRED. A quote can only be used once (QUOTE_USED) and only for the same route, vehicle and pickup time (QUOTE_MISMATCH). If you skip quote_id, GO prices the delivery at the moment you create it.
Create a delivery
POST /v1/delivery/deliveries needs an Idempotency-Key header and an X-GO-User header (your own id for the person ordering). It dispatches through the normal GO flow: nearby drivers are rung, the first to accept at the quoted price takes it, and pickup and drop-off codes and photo proof work as in the GO app. codes.dropoff is what your customer tells the driver at hand-over. Cash on delivery is not supported.
The response includes vehicle_type, quote_id, the final price and, for scheduled deliveries, scheduled_for and cancel_window. Pass your own order number as partner_ref to find the delivery later.
Statuses: pending (also while a scheduled delivery waits) → assigned → picked_up → in_transit → delivered; or cancelled or failed.
Scheduled delivery
Add scheduled_for (an ISO 8601 time with its zone, for example 2026-10-30T09:30:00+01:00) to the quote and the delivery to book a later pickup. Plans that include Scheduled delivery only. Others get 403 PLAN_FEATURE_REQUIRED.
- At least 15 minutes from now and at most 30 days ahead (
SCHEDULE_INVALIDotherwise). - The delivery waits as
pendingwithstatus_detail: "scheduled"and is sent to drivers close to the pickup time. - Cancel window: free until 60 minutes before pickup. The delivery shows
cancel_window.free_untilandfree_now. After that the normal cancellation rules apply. - The quote and the delivery must carry the same
scheduled_for.
curl -X POST https://api.go.mendifier.com/api/v1/delivery/quotes -H "Authorization: Bearer $GO_KEY" -H "Content-Type: application/json" -d '{
"pickup": {…}, "dropoff": {…}, "vehicle_type": "car", "scheduled_for": "2026-10-30T09:30:00+01:00" }'Track and cancel
curl https://api.go.mendifier.com/api/v1/delivery/deliveries/dlv_abc123 -H "Authorization: Bearer $GO_KEY" -H "X-GO-User: usr_8321"
curl "https://api.go.mendifier.com/api/v1/delivery/deliveries?status=in_transit&limit=20" -H "Authorization: Bearer $GO_KEY"
curl -X POST https://api.go.mendifier.com/api/v1/delivery/deliveries/dlv_abc123/cancel -H "Authorization: Bearer $GO_KEY" \
-H "X-GO-User: usr_8321" -H "Content-Type: application/json" -d '{"reason":"Customer changed their mind"}'
A delivery has the driver's first name and rating, the vehicle, a live location, proof photos, events, and a public tracking_url for the recipient. Driver phone numbers are never returned. You can cancel until the parcel is picked up (409 NOT_CANCELLABLE afterwards).
Connecting users
The first time someone orders, send who they are (inline as user on the create call, or with POST /v1/delivery/users): external_user_id, name, phone, optional email, and consent_accepted: true with your consent_terms_ref. GO creates or links their GO account in the background. Never send a password. In test mode no GO account is created.
Webhooks
Webhooks are included in plans that list Signed webhooks (see the table below). Set an HTTPS URL in the dashboard or with PUT /v1/delivery/webhook. On plans without webhooks, poll GET /v1/delivery/deliveries/:id instead.
Events: delivery.assigned, delivery.picked_up, delivery.in_transit, delivery.delivered, delivery.cancelled, delivery.failed, and webhook.test.
{ "id":"evt_…", "object":"event", "type":"delivery.delivered", "created":"2026-10-28T10:15:00.000Z", "livemode":true,
"data": { "object": { "id":"dlv_abc123", "status":"delivered", "vehicle_type":"motorcycle", … } } }
Check the signature
Every webhook is signed with your whsec_… secret (shown once; rotate it any time). Two headers carry a signature, so use whichever suits you:
| Header | Value |
|---|---|
X-GO-Signature | sha256=<hex>: HMAC-SHA256 of the raw request body. |
GO-Signature | t=<unix>,v1=<hex>: HMAC-SHA256 of "<t>.<raw body>". Reject timestamps older than 5 minutes to stop replays. |
GO-Event-Id, GO-Event-Type | Use the id to ignore duplicates. |
const crypto = require('crypto');
// rawBody must be the exact bytes you received, before any JSON parsing
function verify(rawBody, headers, secret) {
const mac = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
const got = (headers['x-go-signature'] || '').replace('sha256=', '');
return got.length === mac.length && crypto.timingSafeEqual(Buffer.from(mac), Buffer.from(got));
}
Reply 2xx within 8 seconds. Otherwise GO retries after 30 seconds, 2 minutes, 10 minutes, 1 hour and 6 hours, then marks the event failed. Events can arrive more than once: de-duplicate on id. See the log with GET /v1/delivery/webhook-events and send a test with POST /v1/delivery/webhooks/test.
Idempotency
Send an Idempotency-Key (a UUID is ideal) with every create call. If your request times out, send it again with the same key and body: you get the same delivery back (200, header Idempotent-Replayed: true) and no second delivery. The same key with a different body is 409 IDEMPOTENCY_KEY_REUSED. Use partner_ref for your own order number, so you can look it up later.
Rate limits
Each plan has a limit per minute per key. Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset. Over the limit you get 429 RATE_LIMITED with a Retry-After header. The limits are in the plan table below. Enterprise limits can be set per key.
Usage and billing
Plans are billed monthly in naira through Paystack from the dashboard. Each plan includes a number of deliveries and API calls a month. Beyond that you pay a small overage, invoiced after the period closes. Delivery fares are passed through at the price you were quoted. Every month you get an invoice (GOP-YYYY-NNNNN) for usage and one for renewal. If a saved card is on file it is charged automatically.
- Pay an invoice within the grace period and nothing changes. Unpaid, live keys stop working with
402 SUBSCRIPTION_REQUIREDuntil you pay. Test keys keep working. - Usage reports (plans that include them):
GET /v1/delivery/usage?from=2026-10-01&to=2026-10-31returns calls per key and day, deliveries and fares. GET /v1/delivery/statementlists delivered live deliveries (JSON or CSV).
Plans and features
What each plan includes, from the same data the API enforces. The numbers are the current list, and GO may change them.
Loading plans…
If a call needs something your plan does not include, you get a clear error instead of a surprise charge:
HTTP 403
{ "success": false, "error": { "code": "PLAN_FEATURE_REQUIRED", "message": "Scheduled delivery is not included in your Starter plan. Upgrade to Growth.",
"feature": "scheduled", "current_plan": "starter", "required_plan": "growth" } }
Test mode
A test key (or X-GO-Mode: test on a live one) does everything except the real world: no GO account, no driver, no SMS, no bill. Test keys are free on every plan. Move a test delivery forward with POST /v1/delivery/deliveries/:id/simulate {"to":"delivered"}, firing each webhook in order, with a clearly labelled test driver.
Errors
| HTTP | Code | Meaning |
|---|---|---|
| 401 | MISSING_CREDENTIAL, INVALID_API_KEY, INVALID_TOKEN | No key, an unknown or revoked key, or a bad token. |
| 402 | SUBSCRIPTION_REQUIRED | Live keys need an active plan. The plan is unpaid or has lapsed. |
| 403 | PLAN_FEATURE_REQUIRED | Your plan does not include this vehicle or feature. The body names the feature and the plan that has it. |
| 403 | PARTNER_NOT_APPROVED, PARTNER_SUSPENDED, USER_UNLINKED | Account status. Contact GO. |
| 400 | UNKNOWN_VEHICLE_TYPE, SCHEDULE_INVALID, MISSING_USER, CONSENT_REQUIRED, OUTSIDE_SERVICE_AREA | The request needs fixing. message says what. |
| 404 | NOT_FOUND, QUOTE_NOT_FOUND | Unknown id or quote. |
| 409 | QUOTE_EXPIRED, QUOTE_USED, QUOTE_MISMATCH, IDEMPOTENCY_KEY_REUSED, NOT_CANCELLABLE, KEY_LIMIT | Get a new quote, use a new key, or the state does not allow it. KEY_LIMIT: your plan's live key allowance is used up. |
| 429 | RATE_LIMITED | Slow down. See Retry-After. |
The full machine-readable spec is at /v1/delivery-docs/openapi.json.