Dlivri Business

API reference

Create and track deliveries from your own website or app. All requests use JSON over HTTPS.

Authentication

Create keys in the portal under Developers. Send them as a bearer token. dlv_test_… keys work immediately, never dispatch couriers and never charge your wallet. dlv_live_… keys need a verified business.

curl https://dlivri.auvia.business/api/v1/wallet \
  -H "Authorization: Bearer dlv_test_xxxxxxxx"

Errors return an HTTP status and { "error": { "code", "message" } }. Rate limit: 300 requests per minute per key.

Orders and legs

An order is one job for one customer. It has one or two legs (courier trips):

Addresses accept latitude/longitude plus an optional ghanaPostCode. Resolve a GhanaPost GPS address first with the address endpoint. Each leg has a 4-digit pin that the courier must collect at hand-over, unless you set proofMode: "photo_only".

GET/addresses/ghanapost/{code}

Resolve a GhanaPost GPS address to coordinates.

curl https://dlivri.auvia.business/api/v1/addresses/ghanapost/GA-492-6343 -H "Authorization: Bearer $KEY"

{ "object": "address", "code": "GA-492-6343", "latitude": 5.65363, "longitude": -0.19927,
  "region": "Greater Accra", "district": "Ayawaso West", "area": "University Of Ghana" }

GET/addresses/reverse?lat=&lng=

The GhanaPost GPS address for a point.

POST/quotes

Price an order without booking it. Same body as POST /orders.

POST/orders

Create an order (or up to 100 with { "orders": [...] }). Live orders are charged to your wallet. Send an Idempotency-Key header to make retries safe.

curl -X POST https://dlivri.auvia.business/api/v1/orders \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-LAU-2002" \
  -d '{
    "type": "round_trip",
    "reference": "LAU-2002",
    "facilityIndex": 0,
    "customer": {
      "name": "Kofi Mensah", "phone": "0201234567",
      "place": { "latitude": 5.6355, "longitude": -0.1612, "address": "East Legon", "ghanaPostCode": "GA-412-3456" }
    },
    "package": { "size": "medium", "category": "other", "description": "Bag of laundry" },
    "vehicleType": "motorbike",
    "return": { "mode": "on_release" }
  }'

Fields: facilityIndex (a saved facility) or an inline facility; package.size is small, medium or large; vehicleType is motorbike, tricycle or car (defaults to the best fit);pickupAt (ms epoch) schedules the first leg; proofMode; notes.

{
  "id": "q7Yc…", "object": "order", "livemode": true, "type": "round_trip",
  "status": "in_progress", "total_charged": 37.55, "currency": "GHS",
  "legs": [
    { "id": "tr_1…", "kind": "pickup", "status": "searching", "fare": 18.84, "pin": "4821", "tracking_url": "https://auvia.business/track/…" },
    { "id": "tr_2…", "kind": "return", "status": "awaiting_release", "fare": 18.71, "pin": "0937", "tracking_url": "…" }
  ]
}

GET/orders/{id}

Order with live courier details, location and proof-of-delivery photos per leg.

GET/orders?reference={yourRef}

Look up orders by your own reference, or list recent orders (limit up to 100).

POST/orders/{id}/release

Send the return leg of a round trip once the items are ready (after the pickup leg is delivered).

POST/orders/{id}/cancel

Cancel every leg not yet picked up. Their fares are refunded to your wallet.

GET/wallet

Current wallet balance in GHS.

POST/test/orders/{id}/advance

Test keys only: move a test order’s next leg forward one step and fire the matching webhooks.

Webhooks

Set an https endpoint under Developers. Events: order.created, leg.courier_assigned, leg.picked_up,leg.delivered, leg.failed, order.awaiting_return, order.completed, order.cancelled. Failed deliveries are retried up to 5 times. Respond with any 2xx status.

Verify each request: X-Dlivri-Signature: t=<unix>,v1=<hex> where v1 is HMAC-SHA256 of <t>.<raw body> with your signing secret.

// Node.js
const crypto = require('crypto');
function verify(rawBody, header, secret) {
  const { t, v1 } = Object.fromEntries(header.split(',').map((p) => p.split('=')));
  const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
  return crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected)) && Date.now() / 1000 - Number(t) < 300;
}