Introduction

The AnchorRide API lets you fetch shipping rates and create prepaid deliveries through Okada's own network of active riders. Every order is a real delivery — the same rider pool that handles ordinary Okada rides.

The base URL for all API requests is:

https://api.anchorride.name.ng/api/anchorride/v1

All request and response bodies are JSON. All amounts are in Nigerian Naira (NGN).

Authentication

Every request to the API is authenticated with your API key, sent as a Bearer token in the Authorization header:

Authorization: Bearer ar_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Get your API key from your dashboard after signing up. Keys are only ever shown once, at the moment they're created — store it somewhere safe.

You can create additional keys or revoke a key at any time from your dashboard without affecting your other keys.

Sandbox mode

Every API key comes in two flavors: ar_live_... keys create real, billed deliveries. ar_test_... keys let you build and test your integration risk-free — orders created with a test key skip the wallet debit entirely and are never dispatched to a real driver.

You can generate a test key from your dashboard alongside your live keys.

Simulating status changes

POST /orders/:id/simulate

Since a test order never reaches a real driver, use this endpoint to manually advance it through the delivery lifecycle — it fires the exact same webhook and email notifications a real status change would, so you can test your integration end to end. Only available for orders created with a test key.

Request body

FieldTypeDescription
statusstringRequired. One of matched, driver_en_route, in_progress, completed, cancelled.

Example request

POST /orders/7/simulate
Content-Type: application/json

{
  "status": "driver_en_route"
}

Get a rate

POST /rates

Returns a real fare quote for a pickup and dropoff, based on actual road distance — the exact same fare an order created with these coordinates will be charged.

Request body

FieldTypeDescription
pickupobjectRequired. { latitude, longitude }
dropoffobjectRequired. { latitude, longitude }

Example request

POST /rates
Content-Type: application/json

{
  "pickup": { "latitude": 7.398657, "longitude": 3.9495069 },
  "dropoff": { "latitude": 7.417546, "longitude": 3.964764 }
}

Example response

200 OK

{
  "quoteId": "e41decb7-6ecb-43a7-bd01-37cacb617e47",
  "quoteExpiresInSeconds": 300,
  "fare": 1233,
  "currency": "NGN",
  "distanceKm": 4.88,
  "durationMin": 11
}
quoteId locks in this exact fare for 5 minutes. Pass it as quoteId when you call /orders to guarantee you're charged this price, even if it changes in the meantime. Omitting it is fine too — the order will simply be priced fresh at creation time, same as before.

Create an order

POST /orders

Creates a real delivery and debits the fare from your wallet balance immediately. There's no separate confirmation step — call /rates first to see the fare, then call this endpoint to actually book it at that price.

Headers

HeaderDescription
Idempotency-KeyOptional. Any string unique to this order attempt. If a request with the same key is retried within 24 hours (e.g. after a timeout or network error), the original response is returned and no duplicate order or charge is created.

Request body

FieldTypeDescription
pickupobjectRequired. { latitude, longitude }
dropoffobjectRequired. { latitude, longitude }
quoteIdstringOptional. A quoteId returned from a prior call to /rates. If provided and still valid (under 5 minutes old, not already used), the order is charged at that exact locked fare instead of a freshly computed one.
pickupLabelstringOptional. Human-readable pickup address, shown to the driver.
dropoffLabelstringOptional. Human-readable dropoff address, shown to the driver.
detailsstringOptional. Notes about the package for the driver.
externalReferencestringOptional. Your own order ID, returned back to you on every lookup and webhook so you can match it to your own system.

Example request

POST /orders
Content-Type: application/json
Idempotency-Key: order-1001-attempt-1

{
  "pickup": { "latitude": 7.398657, "longitude": 3.9495069 },
  "dropoff": { "latitude": 7.417546, "longitude": 3.964764 },
  "quoteId": "e41decb7-6ecb-43a7-bd01-37cacb617e47",
  "pickupLabel": "Warehouse, Iwo Road",
  "dropoffLabel": "12 Bodija Estate",
  "externalReference": "ORDER-1001"
}

Example response

201 Created

{
  "orderId": 2,
  "rideId": 66,
  "externalReference": "ORDER-1001",
  "status": "requested",
  "fare": 1233,
  "currency": "NGN",
  "distanceKm": 4.88,
  "dropoffPin": "2360"
}
dropoffPin is a 4-digit code the driver will ask for when they arrive at the dropoff address, to confirm the right person received the package. Pass this along to your customer however you normally communicate with them — an order confirmation SMS, an email receipt, or in your app.

Insufficient balance

If your wallet balance doesn't cover the fare, the order is not created and no charge occurs:

402 Payment Required

{
  "message": "Insufficient wallet balance.",
  "required": 1233,
  "available": 500
}

Get order status

GET /orders/:id

Looks up an order by its AnchorRide order ID (the orderId returned when you created it).

Example response

200 OK

{
  "orderId": 2,
  "externalReference": "ORDER-1001",
  "status": "in_progress",
  "fare": 1233,
  "distanceKm": 4.88,
  "createdAt": "2026-08-23T06:15:09.367Z"
}

Possible status values

StatusMeaning
requestedOrder created, waiting for a driver.
matchedA driver has been assigned.
driver_en_routeDriver is heading to the pickup address.
in_progressPackage picked up, driver en route to dropoff.
completedDelivered — the driver confirmed the dropoff PIN.
cancelledOrder was cancelled.

List orders

GET /orders

Returns your orders, most recent first, with pagination and optional status filtering.

Query parameters

ParamTypeDescription
limitnumberOptional. Max results per page. Default 20, maximum 100.
offsetnumberOptional. Number of orders to skip. Default 0.
statusstringOptional. Filter to a single status (see the status table above).

Example request

GET /orders?limit=20&offset=0&status=completed

Example response

200 OK

{
  "orders": [
    {
      "orderId": 6,
      "externalReference": "ORDER-1006",
      "status": "completed",
      "fare": 1295,
      "distanceKm": 4.88,
      "createdAt": "2026-08-30T09:12:41.001Z"
    }
  ],
  "pagination": {
    "total": 14,
    "limit": 20,
    "offset": 0,
    "hasMore": false
  }
}

Cancel an order

POST /orders/:id/cancel

Cancels an order, provided a driver hasn't already picked up the package. Whether you're refunded depends on how far along the delivery is:

Order status when cancelledRefund
requestedFull refund to your wallet, immediately.
matchedFull refund to your wallet, immediately.
driver_en_routeNo refund — the driver is already travelling to the pickup address.

Orders that are in_progress, completed, or already cancelled can't be cancelled through this endpoint.

Example response

200 OK

{
  "orderId": 6,
  "status": "cancelled",
  "refunded": true,
  "refundAmount": 1295
}

Order can't be cancelled

409 Conflict

{
  "error": "Order cannot be cancelled",
  "currentStatus": "in_progress",
  "message": "Order has progressed past the point where cancellation is allowed"
}

Order status events

Register a webhook URL from your dashboard to be notified automatically whenever one of your orders changes status, instead of polling GET /orders/:id.

Event types

EventFires when
order.driver_assignedA driver accepts the order.
order.driver_en_routeDriver is heading to pickup.
order.picked_upPackage picked up.
order.deliveredPackage delivered and PIN confirmed.
order.cancelledOrder was cancelled.

Payload

POST https://your-server.com/your-webhook-path
Content-Type: application/json
X-AnchorRide-Signature: 8f2a91c3...

{
  "event": "order.picked_up",
  "orderId": 2,
  "externalReference": "ORDER-1001",
  "status": "in_progress",
  "timestamp": "2026-08-23T06:57:14.221Z"
}

Your endpoint should respond with any 2xx status to acknowledge receipt. Deliveries are logged in your dashboard so you can see what was sent and whether it succeeded.

If your endpoint doesn't respond with 2xx (or doesn't respond at all), AnchorRide retries up to 3 times: immediately, then after 1 minute, then after 5 minutes. Each attempt is logged separately in your dashboard's delivery history. If all 3 attempts fail, the delivery is marked failed and not retried further — GET /orders/:id is always available as a fallback source of truth.

Verifying signatures

Every webhook request includes an X-AnchorRide-Signature header — an HMAC-SHA256 hash of the raw request body, signed with your webhook secret (shown once when you set your webhook URL). Verify it to confirm a request genuinely came from AnchorRide.

Node.js example

const crypto = require("crypto");

function verifySignature(rawBody, signature, secret) {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(rawBody)
    .digest("hex");
  return signature === expected;
}
Compute the signature over the exact raw request body — not a re-serialized copy of the parsed JSON, which can produce different bytes (key order, whitespace) and fail to match.

Errors

Errors return a JSON body with a message field describing what went wrong.

StatusMeaning
400Missing or invalid request fields.
401Missing, invalid, or revoked API key.
402Insufficient wallet balance.
404Order not found.
409Order can't be cancelled in its current status.
500Something went wrong on our end. Safe to retry.