Travo for Developers

Your checkout. Our riders.

Plug Nigeria's delivery network into your store or app: pull a live delivery quote at checkout, create the order the moment payment clears, and stream tracking to your customer — all over a clean REST API with webhooks.

REST + JSON Webhooks for order events Test mode sandbox keys v1 stable
  quote → order in two calls
# 1. Quote the delivery at checkout
curl -G https://api.travo.ng/v1/service-quotes \
  -H "Authorization: Bearer $TRAVO_SECRET_KEY" \
  --data-urlencode "pickup=6.4281,3.4216" \
  --data-urlencode "dropoff=6.6018,3.3515" \
  --data-urlencode "service_type=sendparcel"

# → { "id": "service_quote_...", "amount": 245000, "currency": "NGN" }

# 2. Customer paid — create the order
curl -X POST https://api.travo.ng/v1/orders \
  -H "Authorization: Bearer $TRAVO_SECRET_KEY" \
  -d '{ "service_quote": "service_quote_...", "dispatch": true, ... }'

Overview

The Travo API gives your application direct access to the same logistics engine that powers travo.ng — quoting, dispatch, rider allocation, tracking and proof-of-delivery. It's a straightforward JSON REST API built around four battle-tested concepts — Places, Entities, Orders and Service Quotes — so if you've integrated any modern delivery or payments API, this will feel familiar in minutes.

The typical integration is three steps: quote a delivery when your customer is at checkout, create the order when payment succeeds, then listen on webhooks (or poll) as the order moves from dispatch to delivered — pushing live status to your customer under your own brand.

Base URL: https://api.travo.ng/v1  ·  All requests over HTTPS, all bodies JSON, all monetary amounts in kobo (₦2,450.00 = 245000).

Authentication

Every request is authenticated with a secret API key in the Authorization header. Keys are issued per organisation from the Travo Console (Console → Developers → API Keys) and come in two flavours:

  • travo_test_… — sandbox: full API behaviour, no real dispatch, no charges. Build against this.
  • travo_live_… — production: real quotes, real riders, real money.
Header
Authorization: Bearer travo_live_xxxxxxxxxxxxxxxx
Content-Type: application/json
Keep secret keys server-side only. Never ship them in browser JavaScript or mobile app builds — proxy API calls through your backend. A leaked live key can create real orders billed to your account; rotate any exposed key immediately from the Console.

Don't have keys yet? Request access with your company name and website — integration accounts are approved with a short review.

Quickstart — first quote in 60 seconds

With a test key exported as TRAVO_SECRET_KEY:

bash · curl
curl -G "https://api.travo.ng/v1/service-quotes" \
  -H "Authorization: Bearer $TRAVO_SECRET_KEY" \
  --data-urlencode "pickup=6.4281,3.4216" \
  --data-urlencode "dropoff=6.6018,3.3515" \
  --data-urlencode "service_type=sendparcel"
Response · 200
{
  "id": "service_quote_9f3b2c",
  "amount": 245000,
  "currency": "NGN",
  "service_type": "sendparcel",
  "expires_at": "2026-07-25T16:45:00Z"
}

That's a live price for a Victoria Island → Ikeja parcel: ₦2,450. Hold the id — you'll pass it when creating the order so the customer pays exactly what they were quoted.

1 · Delivery quotes

Query available service quotes for a pickup/dropoff pair. Coordinates (lat,lng) give the most precise pricing; street addresses are geocoded automatically.

GET /v1/service-quotes
ParameterTypeDescription
pickupstringCoordinates "6.4281,3.4216", an address string, or an existing place_… ID
dropoffstringSame formats as pickup
service_typestringOne of the service types, e.g. sendparcel. Omit to get quotes for all services covering the route
scheduled_atISO 8601Optional — future pickup time for scheduled deliveries
total_weightnumberOptional — total kg, used for weight-tiered services (haulage, bulk)

Quotes are firm until expires_at (typically 30 minutes) — long enough for any checkout. Requote if the customer edits their address.

JavaScript · Node/serverless
const res = await fetch(
  "https://api.travo.ng/v1/service-quotes?" + new URLSearchParams({
    pickup: "12 Adeola Odeku St, Victoria Island, Lagos",
    dropoff: customer.address,
    service_type: "sendparcel"
  }),
  { headers: { Authorization: `Bearer ${process.env.TRAVO_SECRET_KEY}` } }
);
const [quote] = await res.json();
// show quote.amount / 100 as the delivery fee at checkout

2 · Create an order

When payment succeeds, turn the quote into a dispatched order. Pass the service_quote so pricing is locked to what the customer saw, the parcel details as entities, and dispatch: true to send it straight to rider allocation.

POST /v1/orders
Request body
{
  "service_quote": "service_quote_9f3b2c",
  "type": "sendparcel",
  "pickup": { "name": "Duro Fashion Store", "address": "12 Adeola Odeku St, VI, Lagos", "phone": "+2348030000000" },
  "dropoff": { "name": "Chiamaka O.", "address": "4 Allen Avenue, Ikeja, Lagos", "phone": "+2348151112222" },
  "entities": [
    { "name": "Ankara gown (order #1042)", "weight": 1.2, "weight_unit": "kg", "declared_value": 4500000 }
  ],
  "meta": { "store_order_id": "1042" },
  "dispatch": true,
  "notes": "Call on arrival; gate code 4421"
}
Response · 201
{
  "id": "order_7d1e4a",
  "status": "created",
  "tracking_number": "TRV-84620-NG",
  "type": "sendparcel",
  "amount": 245000,
  "currency": "NGN",
  "driver_assigned": null,
  "created_at": "2026-07-25T15:04:22Z"
}

Store id and tracking_number against your own order record — meta is a free-form object echoed back on every webhook, which is the cleanest way to reconcile Travo orders with yours.

3 · Tracking & order lifecycle

GET /v1/orders/{id}

Returns the current order state including driver details once assigned (name, phone, live position) and the proof-of-delivery record on completion. Order status moves through:

createddispatcheddriver_assigned picked_upin_transitcompletedcanceled

For customer-facing tracking without exposing your API, link them to the public tracker with the tracking number: https://travo.ng/track/TRV-84620-NG.

4 · Webhooks

Register an HTTPS endpoint in the Console (Developers → Webhooks) and Travo POSTs a JSON event on every order transition — no polling. Events carry the full order object plus your meta.

EventFired when
order.createdOrder accepted into the system
order.dispatchedSent to rider allocation
order.driver_assignedRider confirmed — driver name/phone now on the order
order.picked_upParcel collected (pickup proof recorded)
order.completedDelivered — POD photo/PIN recorded
order.canceledCanceled by merchant or ops, with reason
Webhook payload (order.completed)
{
  "event": "order.completed",
  "created_at": "2026-07-25T17:41:09Z",
  "data": {
    "id": "order_7d1e4a",
    "status": "completed",
    "tracking_number": "TRV-84620-NG",
    "meta": { "store_order_id": "1042" },
    "proof_of_delivery": { "type": "photo+pin", "completed_at": "2026-07-25T17:40:52Z" }
  }
}
Verify before you trust: each webhook is signed — validate the signature header against your webhook secret from the Console, respond 2xx quickly, and treat deliveries as at-least-once (dedupe on data.id + event).

Objects & fields

Place

FieldTypeNotes
namestringContact/location name shown to the rider
addressstringStreet address — geocoded server-side
coordinates[lng, lat]Optional; overrides geocoding when supplied
phonestringE.164 preferred (+234…) — used for rider contact & SMS updates

Entity (parcel/item)

FieldTypeNotes
namestringWhat's being carried — appears on the waybill
weight / weight_unitnumber / stringe.g. 1.2, "kg"
dimensionsobject{length, width, height, unit} — used for volumetric services
declared_valueintegerKobo — drives insurance handling on high-value goods

Service types

The service_type / type values map to Travo's live order configurations — the same ones behind the booking site:

sendparcelhaulagestorefront driverairportpickupexportimport foodcateringutilitiesrubbishcollection breakdownsresponseemergencyservice

Most e-commerce integrations only need sendparcel (same-day/next-day parcels) and occasionally haulage for bulk. Quoting without a service_type returns every service available on the route so you can offer tiers (e.g. standard vs bulk) at checkout.

Errors

Standard HTTP semantics with a consistent JSON error body:

Error body
{ "errors": [ "Dropoff address could not be resolved to a serviceable area." ] }
StatusMeaning
401Missing/invalid API key — check the Bearer header and key environment
404Unknown resource ID (expired quote IDs return this too)
422Validation failed — the errors array names each field problem
429Rate limited — back off and retry with exponential delay
5xxOur side — safe to retry idempotently; creating with the same service_quote won't double-book

E-commerce recipe — quote at checkout, ship on payment

The integration pattern that fits WooCommerce, Shopify (via app proxy), Magento or any custom cart:

  • Checkout loads → your backend calls GET /service-quotes with your store's pickup and the customer's address → render amount/100 as the delivery fee (add your margin if you mark up)
  • Customer pays → payment webhook on your side calls POST /orders with the held quote ID, parcel entities and meta.store_order_id
  • Travo webhooks → update your order status and email/SMS the customer; on order.driver_assigned show rider name and the public tracking link
  • Deliveredorder.completed closes your fulfilment, with POD evidence on record if there's ever a dispute
PHP · order creation on payment success
$ch = curl_init("https://api.travo.ng/v1/orders");
curl_setopt_array($ch, [
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_POST => true,
  CURLOPT_HTTPHEADER => [
    "Authorization: Bearer " . getenv("TRAVO_SECRET_KEY"),
    "Content-Type: application/json"
  ],
  CURLOPT_POSTFIELDS => json_encode([
    "service_quote" => $quoteId,
    "type" => "sendparcel",
    "pickup" => $storePickup,
    "dropoff" => ["name" => $order->customer_name, "address" => $order->address, "phone" => $order->phone],
    "entities" => [[ "name" => "Order #{$order->id}", "weight" => $order->weight_kg, "weight_unit" => "kg" ]],
    "meta" => ["store_order_id" => (string)$order->id],
    "dispatch" => true
  ])
]);
$travoOrder = json_decode(curl_exec($ch), true);

Selling on the network instead of just shipping? Our Storefront platform lets you launch a full branded shop (web + iOS/Android) running on this same engine — talk to us about a hosted build.

Tooling & resources

  • REST-first — no SDK required: every example on this page works with curl, fetch, Guzzle, axios or any HTTP client your stack already uses
  • JavaScript SDK & Postman collection — available on request for typed access to Places, Orders and tracking; message the line with DEV API: and we'll send the package for your stack
  • Branded apps on the same engine — customer-facing storefront apps (web + iOS/Android) and driver apps can be built and hosted on this platform under your brand; ask us for a scoping call
  • Deep integration or custom order flows — bespoke order configurations, custom fields and dedicated environments are available for platform partners

Developer support

Integrating? We'd genuinely like to help you ship it — a working integration makes both of us money. Message the line with the "DEV API:" prefix for routing to someone technical, or email hello@travoservices.com.

DEV API: WhatsApp line Request API keys →

WhatsApp Chat