GO Developers

Build deliveries into your product.

One API to quote, book, track and cancel deliveries in Nigeria, by motorcycle, car, van or truck.

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"}}.

Start free: create a developer account and get a test key in a minute. Live deliveries need a paid plan. See pricing.

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} ] }
FieldNotes
pickup, dropoffaddress, lat, lng. Both must be inside a GO service area.
vehicle_typeOptional. Quote just this vehicle.
scheduled_forOptional ISO time for a later pickup. The price is for that time.
parcel.sizeOptional 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_INVALID otherwise).
  • The delivery waits as pending with status_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_until and free_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:

HeaderValue
X-GO-Signaturesha256=<hex>: HMAC-SHA256 of the raw request body.
GO-Signaturet=<unix>,v1=<hex>: HMAC-SHA256 of "<t>.<raw body>". Reject timestamps older than 5 minutes to stop replays.
GO-Event-Id, GO-Event-TypeUse 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_REQUIRED until you pay. Test keys keep working.
  • Usage reports (plans that include them): GET /v1/delivery/usage?from=2026-10-01&to=2026-10-31 returns calls per key and day, deliveries and fares.
  • GET /v1/delivery/statement lists 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" } }

See pricing

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

HTTPCodeMeaning
401MISSING_CREDENTIAL, INVALID_API_KEY, INVALID_TOKENNo key, an unknown or revoked key, or a bad token.
402SUBSCRIPTION_REQUIREDLive keys need an active plan. The plan is unpaid or has lapsed.
403PLAN_FEATURE_REQUIREDYour plan does not include this vehicle or feature. The body names the feature and the plan that has it.
403PARTNER_NOT_APPROVED, PARTNER_SUSPENDED, USER_UNLINKEDAccount status. Contact GO.
400UNKNOWN_VEHICLE_TYPE, SCHEDULE_INVALID, MISSING_USER, CONSENT_REQUIRED, OUTSIDE_SERVICE_AREAThe request needs fixing. message says what.
404NOT_FOUND, QUOTE_NOT_FOUNDUnknown id or quote.
409QUOTE_EXPIRED, QUOTE_USED, QUOTE_MISMATCH, IDEMPOTENCY_KEY_REUSED, NOT_CANCELLABLE, KEY_LIMITGet 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.
429RATE_LIMITEDSlow down. See Retry-After.

The full machine-readable spec is at /v1/delivery-docs/openapi.json.