Business portal
Partner API integration guide
Everything a business needs to connect their store, marketplace, or ops system to Ataboy — authentication, creating deliveries, tracking, webhooks, and going live.
Base URL: https://api.ataboyexpress.com
Dashboard: /dashboard, Sign up: /signup
1. How the Partner API works
The Partner API lets your backend create and manage deliveries on Ataboy without using the customer booking UI. Typical flow:
- Your checkout or warehouse system collects pickup and dropoff details.
- You call Ataboy to estimate fee, then create a delivery.
- Ataboy assigns a rider and moves the parcel through status updates.
- Your system receives webhook events (or polls track endpoints) and updates your customers.
All partner calls use API Key + API Secret headers. Secrets are shown once when a key is created, store them in your secrets manager, never in frontend code.
2. Account & go-live onboarding
Onboarding follows a Paystack-style model:
- Create a business account on this portal — sign up here.
- You immediately receive test keys (
atb_test_…). - Build and test your integration against local/test traffic.
- In the dashboard, open Compliance and submit CAC, address, director ID, and use case.
- After Ataboy admin approval, generate live keys (
atb_live_…) under Integration Hub → Credentials.
3. Test vs live keys
| Mode | Key prefix | When available | Use for |
|---|---|---|---|
| Test | atb_test_ | Immediately after signup | Integration, QA, staging |
| Live | atb_live_ | After compliance approval | Production customer orders |
403 and tells you to use test keys.4. Integration Hub (partner differences)
The Partner API stays the same for every business. Differences (custom checkout rates, markup, connectors) live in the Integration Hub on the Business dashboard.
Your store checkout
│
▼
Integration Hub (rate cards / markup / connectors)
│
▼
Stable Partner API (/v1/partner/*)
│
▼
Ataboy logisticsPricing modes
ataboy_default, platform formula (distance, weight, vehicle)custom_rates, fixed fees by destination LGA and optional place (Correct Gadgets–style). Place beats LGA so Lakowe ≠ Dangote Refinery.markup, Ataboy default + your % markup (optional free delivery threshold)
Configure in Business dashboard → Integration Hub. Estimate and create still use POST /v1/partner/deliveries/estimate and POST /v1/partner/deliveries — only the fee resolution changes. Responses include pricingSource and optional matchedRateName.
For place-level rates, send dropoff.lga and dropoff.place (e.g. Ibeju-Lekki + Lakowe). Matching order: place → LGA → legacy city zones → Ataboy default. Optional packageSize (small | medium | large); if omitted, size is inferred from weightKg.
5. Authentication
Send these headers on every /v1/partner/* request:
X-Api-Key: atb_test_xxxxxxxxxxxx X-Api-Secret: <your_secret> Content-Type: application/json
Security rules
- Only call the Partner API from your server (never from a browser app).
- Rotate keys from the Business dashboard if a secret is leaked.
- Revoke unused keys; prefer separate keys for staging vs production.
6. Quickstart: your first request
Estimate a fee with a demo test key:
curl -X POST https://api.ataboyexpress.com/v1/partner/deliveries/estimate \
-H "Content-Type: application/json" \
-H "X-Api-Key: atb_test_fmdemo01" \
-H "X-Api-Secret: demo_secret_freshmart_00000001" \
-d '{
"pickup": { "city": "Lagos", "state": "Lagos" },
"dropoff": { "city": "Lagos", "state": "Lagos" },
"weightKg": 2,
"vehicle": "bike",
"insurance": false
}'A successful response looks like:
{
"estimate": {
"baseFee": 800,
"distanceFee": 400,
"weightFee": 100,
"insuranceFee": 0,
"total": 1300,
"currency": "NGN",
"etaMinutes": 45
}
}7. Estimate a delivery fee
POST /v1/partner/deliveries/estimate
Use this before checkout so customers see the delivery fee. Required fields:
pickup.city,pickup.state(optionallat/lng)dropoff.city,dropoff.state(optionallat/lng)- Optional:
weightKg(default 1),vehicle(bike|car|van),insurance
8. Create a delivery
POST /v1/partner/deliveries
Creates a delivery under your enterprise account. Ataboy returns the delivery object plus the fee estimate used. Pass receiverEmail so Ataboy can email the shopper a delivered confirmation from Ataboy Express (with a no-login Rate this delivery button) when the order completes.
curl -X POST https://api.ataboyexpress.com/v1/partner/deliveries \
-H "Content-Type: application/json" \
-H "X-Api-Key: atb_test_fmdemo01" \
-H "X-Api-Secret: demo_secret_freshmart_00000001" \
-d '{
"pickup": {
"street": "12 Admiralty Way",
"city": "Lagos",
"state": "Lagos",
"country": "Nigeria",
"landmark": "Near Shoprite"
},
"dropoff": {
"street": "15 Adeola Odeku",
"city": "Lagos",
"state": "Lagos",
"country": "Nigeria"
},
"receiverName": "Chioma Okeke",
"receiverPhone": "+2348012345678",
"receiverEmail": "chioma@example.com",
"description": "Grocery order #1042",
"weightKg": 3,
"vehicle": "bike",
"insurance": false
}'Important response fields
delivery.id, store this in your system as the Ataboy delivery referencedelivery.trackingNumber, share with customers for trackingdelivery.status, starts as pending / awaiting dispatchdelivery.fee, fee charged for the trip (NGN)delivery.deliveryCode, arrival confirmation; custody photos prove parcel condition
description (e.g. Order #1042) so support can reconcile Ataboy deliveries with your OMS.9. List, track, and cancel
List your deliveries
GET /v1/partner/deliveries, returns deliveries created with your enterprise account.
Get one delivery + timeline
GET /v1/partner/deliveries/:id, includes delivery and status events.
Track by tracking number
GET /v1/partner/track/:trackingNumber, useful for customer support UIs. May include rider summary (status, vehicle, rating) when assigned.
curl https://api.ataboyexpress.com/v1/partner/track/ATB-XXXXXX \ -H "X-Api-Key: atb_test_fmdemo01" \ -H "X-Api-Secret: demo_secret_freshmart_00000001"
Cancel a delivery
POST /v1/partner/deliveries/:id/cancel
You cannot cancel deliveries that are already delivered or cancelled (400).
10. Webhooks (status updates)
Prefer webhooks over polling. Configure them in the Business dashboard (Integration Hub → Webhooks) or via API:
GET /v1/partner/webhooksPUT /v1/partner/webhooks
Configure via API
curl -X PUT https://api.ataboyexpress.com/v1/partner/webhooks \
-H "Content-Type: application/json" \
-H "X-Api-Key: atb_test_fmdemo01" \
-H "X-Api-Secret: demo_secret_freshmart_00000001" \
-d '{
"url": "https://your-app.com/webhooks/ataboy",
"events": [
"delivery.created",
"delivery.assigned",
"delivery.picked_up",
"delivery.in_transit",
"delivery.completed",
"delivery.cancelled",
"delivery.failed"
],
"active": true,
"rotateSecret": true
}'When rotateSecret is true (or on first create), the response includes webhook.secret once, save it.
Events you can subscribe to
These fire automatically as a delivery moves through its lifecycle:
delivery.created, we accepted your order and it is waiting for a riderdelivery.assigned, a rider took the job; theriderobject is now populateddelivery.picked_up, the rider has your packagedelivery.in_transit, on the way to the receiverdelivery.completed, handed over;completedAtis setdelivery.cancelled, cancelled by you or by Ataboydelivery.failed, the rider could not complete the drop-offdelivery.updated, delivery details changed, e.g. the delivery code was regenerated
These are accepted on subscription and reserved for upcoming milestones, but are not dispatched yet — subscribe now if you want them, just don’t build a flow that waits on them: delivery.accepted, delivery.pickup.arrived, delivery.arriving, delivery.returned, payment.completed, payment.failed.
delivery.delivered is deprecated in favour of delivery.completed. Existing subscriptions to it keep working, but the event name on the wire is always delivery.completed, so match on both if you are migrating.Headers Ataboy sends
| Header | Example | Notes |
|---|---|---|
Content-Type | application/json | Body is always JSON. |
X-Ataboy-Event | delivery.completed | The event name, identical to the event field in the body. |
X-Ataboy-Signature | sha256=<hex> | HMAC-SHA256 of the raw request body, keyed with your webhook secret. |
X-Ataboy-Timestamp | 1786213329 | Unix seconds when the request was signed. Reject anything older than 5 minutes. |
X-Ataboy-Delivery | <uuid> | Unique ID for this delivery attempt. Use it as an idempotency key, retries of the same event arrive with a new value, so de-duplicate on the body's id instead. |
Payload envelope
Every webhook has the same four top-level keys. id is unique per event and is the value to de-duplicate on, a retried event keeps the same id.
id, string (uuid), unique ID for this eventevent, string, the event namecreatedAt, string, ISO 8601 timestamptimestamp, number, the same moment in Unix secondsdata, object, the delivery snapshot described below
Example payload
A delivery.completed event with a rider attached. Every field listed below is always present on delivery events, using null rather than omission when there is no value:
{
"id": "7f1c0f0e-2a3b-4c5d-8e9f-0a1b2c3d4e5f",
"event": "delivery.completed",
"createdAt": "2026-08-08T14:22:09.145Z",
"timestamp": 1786213329,
"data": {
"deliveryId": "3a9d1b52-1f47-4a26-9c3e-77b0a1d4e812",
"businessOrderId": "DC-10482",
"trackingNumber": "ATB-8F3K2Q",
"status": "delivered",
"fee": 2150,
"receiverName": "Chioma Okeke",
"receiverPhone": "+2348031234567",
"pickupCity": "Lagos",
"dropoffCity": "Lagos",
"pickupLga": "Eti-Osa",
"pickupPlace": "Lekki Phase 1",
"dropoffLga": "Ikeja",
"dropoffPlace": "Allen Avenue",
"pricingSource": "partner_rate_card",
"matchedRateName": "Lekki to Ikeja (Bike)",
"trackingUrl": "https://track.ataboyexpress.com/ATB-8F3K2Q",
"deliveryCode": "4821",
"custodyPhotoUrl": "https://cdn.ataboyexpress.com/custody/sample.jpg",
"custodyPhotoUrls": [
"https://cdn.ataboyexpress.com/custody/sample.jpg",
"https://cdn.ataboyexpress.com/custody/sample-2.jpg"
],
"completedAt": "2026-08-08T14:22:07.902Z",
"rider": {
"id": "b2c7f5a1-9e34-4c88-bb10-6d2f0a5c7e93",
"name": "Emeka Nwosu",
"phone": "+2348090001122",
"avatarUrl": "https://cdn.ataboyexpress.com/riders/b2c7f5a1.jpg",
"vehicle": "bike",
"plateNumber": "LAG-472-KJA"
}
}
}Fields in data
| Field | Type | Description |
|---|---|---|
deliveryId | string (uuid) | Ataboy's ID for the delivery. Store it against your order. |
businessOrderId | string | null | Your own order reference. Taken from externalOrderId when you set it on create, otherwise parsed out of description. Null if neither is present. |
trackingNumber | string | Public tracking code, e.g. ATB-8F3K2Q. |
status | string | Delivery status at the time of the event: pending, assigned, picked_up, in_transit, delivered, cancelled or failed. |
fee | number | Delivery fee in naira (not kobo). |
receiverName | string | Name of the person receiving the package. |
receiverPhone | string | Receiver's phone number in E.164 format. |
receiverEmail | string | null | Receiver's email when provided on create. Used by Ataboy to send delivered confirmation + review link. |
pickupCity | string | Pickup city. |
dropoffCity | string | Dropoff city. |
pickupLga | string | null | Pickup local government area, when the address resolved to one. |
pickupPlace | string | null | Pickup landmark or estate, when known. |
dropoffLga | string | null | Dropoff local government area, when the address resolved to one. |
dropoffPlace | string | null | Dropoff landmark or estate, when known. |
pricingSource | string | null | How the fee was calculated: ataboy_default, partner_rate_card or partner_markup. |
matchedRateName | string | null | Name of the Integration Hub rate card row that priced this delivery, or the markup label. Null on default pricing. |
trackingUrl | string | Customer-facing tracking page for this delivery. |
deliveryCode | string | 4-digit code the receiver reads out to the rider to confirm handover. Safe to show the receiver, never the public. |
custodyPhotoUrl | string | null | Latest chain-of-custody photo (parcel condition at pickup, or proof at delivery). Null until a photo is uploaded. |
custodyPhotoUrls | string[] | All custody photos from the latest capture (pickup may include up to 5). Empty array until photos are uploaded. |
completedAt | string | null | ISO timestamp of completion. Only set once status is delivered, null otherwise. |
rider | object | null | Assigned rider, or null before assignment. See the rider fields below. |
Fields in data.rider
rider is null on delivery.created and populated from delivery.assigned onwards.
| Field | Type | Description |
|---|---|---|
rider.id | string (uuid) | Ataboy rider ID. |
rider.name | string | null | Rider's full name. |
rider.phone | string | null | Rider's phone number, for customer support calls. |
rider.avatarUrl | string | null | Rider photo, safe to show the customer. |
rider.vehicle | string | bike, car, van or truck. |
rider.plateNumber | string | null | Vehicle plate number. |
Test events
Sending a test event from your dashboard delivers the same envelope with a stub body, so guard against it before touching your order records:
{
"id": "0c9a...",
"event": "delivery.assigned",
"createdAt": "2026-08-08T14:22:09.145Z",
"timestamp": 1786213329,
"data": {
"test": true,
"message": "Ataboy Express Integration Hub test event"
}
}Verify signatures (Node.js)
Always verify using the raw request body (not a re-serialized JSON object):
import crypto from "node:crypto";
function verifyAtaboySignature(rawBody, signatureHeader, secret) {
const expected = crypto
.createHmac("sha256", secret)
.update(rawBody)
.digest("hex");
const provided = (signatureHeader || "").replace(/^sha256=/, "");
const a = Buffer.from(expected);
const b = Buffer.from(provided);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}Also reject requests where X-Ataboy-Timestamp (or the body’s timestamp) is more than 5 minutes away from your clock, so a captured request cannot be replayed.
Retries & what to respond
Reply 2xx as soon as you have stored the event and do the real work afterwards. Anything else , a non-2xx status or no response within 10 seconds, counts as a failure.
- Up to 5 attempts per event
- Exponential backoff between attempts: 250ms, 500ms, 1s, then 2s
- 10 second timeout on each attempt
- The first 4000 characters of your response body are kept in the delivery log
id can arrive more than once, and events can arrive out of order. Treat handlers as idempotent: key off data.deliveryId and ignore an event whose status is behind the one you already recorded.11. Errors & status codes
| Code | Meaning | What to do |
|---|---|---|
400 | Validation / business rule error | Fix payload (or don’t cancel completed deliveries) |
401 | Missing/invalid API credentials | Check key + secret; ensure key is active |
403 | Live key locked / suspended | Use test keys or finish compliance |
404 | Delivery / tracking not found | Confirm ID belongs to your account |
Error body shape:
{ "message": "Invalid API credentials" }12. Go-live checklist
- Estimate + create delivery works with test keys
- Your OMS stores
delivery.idandtrackingNumber - Webhook endpoint verifies signatures and handles all subscribed events
- Cancel flow tested for pending/assigned orders
- Compliance submitted and approved in Business dashboard
- Live keys generated and stored securely (test keys kept for staging)
- Production base URL and webhook URL updated
13. Demo credentials
Use these against https://api.ataboyexpress.com while developing:
- FreshMart (test only)
Key:atb_test_fmdemo01
Secret:demo_secret_freshmart_00000001 - DrinkCravings (test)
Key:atb_test_dcdemo01
Secret:demo_secret_drinkcravings_test1 - DrinkCravings (live, compliance approved)
Key:atb_live_dcdemo01
Secret:demo_secret_drinkcravings_0001
Use the test and live API keys from your Partner dashboard after signup. Live keys require compliance approval.

