API reference
Quotes & shipments
Get a quote with a price and ETA, then book the shipment against your wallet.
Last updated
Booking a delivery takes two calls. First request a quote, then create the shipment with the quote's quoteId. Quoting first confirms that we deliver to both addresses and locks the price you show your customer.
Service types
Every quote and shipment needs a serviceType:
serviceType | What it means | Weight limit |
|---|---|---|
consolidated | Your parcel shares a vehicle with other parcels on the same route. Billed by route and weight | 0.01 kg to 200 kg |
dedicated | A full vehicle reserved for your load. Billed by route and vehicle | 0.01 kg to 4,500 kg |
See Pricing for the vehicles used and how each service is priced.
Get a quote
Auth: API key. Returns a price, an ETA and a quoteId that is valid for 10 minutes and can be used once.
Request fields
| Field | Type | Required | Description |
|---|---|---|---|
serviceType | string | Yes | consolidated or dedicated |
packageWeightKg | number | Yes | Weight in kilograms. 0.01 to 200 for consolidated, 0.01 to 4,500 for dedicated |
pickupAddress | string | Yes | Street address of the pickup, in Nigeria |
pickupCity | string | Yes | Town or area of the pickup, for example Ikeja |
pickupState | string | Yes | State of the pickup. Lagos, Lagos State, FCT and Abuja are all accepted |
dropoffAddress | string | Yes | Street address of the recipient, in Nigeria |
dropoffCity | string | Yes | Town or area of the dropoff |
dropoffState | string | Yes | State of the dropoff |
declaredValue | number | No | Value of the goods in NGN, used for insurance cover |
packageDescription | string | No | What is in the package, for example Shoes |
Example request
curl -X POST "https://api.7feastr.com/api/v1/logistics/v1/rates/estimate" \
-H "Authorization: Bearer $SEVENFEASTR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"serviceType": "consolidated",
"packageWeightKg": 2,
"pickupAddress": "14 Allen Avenue",
"pickupCity": "Ikeja",
"pickupState": "Lagos",
"dropoffAddress": "8 Admiralty Way",
"dropoffCity": "Lekki",
"dropoffState": "Lagos",
"declaredValue": 45000,
"packageDescription": "Shoes"
}'
// Node.js 18+ (run as an ES module, or inside an async function)
const BASE_URL = "https://api.7feastr.com/api/v1/logistics/v1";
const res = await fetch(`${BASE_URL}/rates/estimate`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SEVENFEASTR_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
serviceType: "consolidated",
packageWeightKg: 2,
pickupAddress: "14 Allen Avenue",
pickupCity: "Ikeja",
pickupState: "Lagos",
dropoffAddress: "8 Admiralty Way",
dropoffCity: "Lekki",
dropoffState: "Lagos",
declaredValue: 45000,
packageDescription: "Shoes",
}),
});
const body = await res.json();
if (!body.success) throw new Error(`${body.error.code}: ${body.error.message}`);
const quote = body.data;
console.log(quote.quoteId, quote.pricing.total, quote.etaAt);
import os
import requests
BASE_URL = "https://api.7feastr.com/api/v1/logistics/v1"
res = requests.post(
f"{BASE_URL}/rates/estimate",
headers={"Authorization": f"Bearer {os.environ['SEVENFEASTR_API_KEY']}"},
json={
"serviceType": "consolidated",
"packageWeightKg": 2,
"pickupAddress": "14 Allen Avenue",
"pickupCity": "Ikeja",
"pickupState": "Lagos",
"dropoffAddress": "8 Admiralty Way",
"dropoffCity": "Lekki",
"dropoffState": "Lagos",
"declaredValue": 45000,
"packageDescription": "Shoes",
},
timeout=30,
)
body = res.json()
if not body["success"]:
raise RuntimeError(f"{body['error']['code']}: {body['error']['message']}")
quote = body["data"]
print(quote["quoteId"], quote["pricing"]["total"], quote["etaAt"])
Example response
200 OK
{
"success": true,
"message": "OK",
"sandbox": true,
"data": {
"quoteId": "QUOTE-9C41E07B2D5F4A18",
"quoteExpiresAt": "2026-10-21T09:40:00.000Z",
"tripType": "intrastate",
"serviceType": "consolidated",
"pickup": {
"address": "14 Allen Ave, Ikeja, Lagos, Nigeria",
"city": "Ikeja",
"state": "Lagos",
"lat": 6.6018,
"lng": 3.3515
},
"dropoff": {
"address": "8 Admiralty Way, Lekki Phase 1, Lagos, Nigeria",
"city": "Lekki",
"state": "Lagos",
"lat": 6.4474,
"lng": 3.4706
},
"distanceKm": 27.4,
"packageWeightKg": 2,
"declaredValue": 45000,
"etaRawMinutes": 58,
"etaBufferedMinutes": 1440,
"etaDays": 1,
"etaAt": "2026-10-22T09:30:00.000Z",
"pricing": { "subtotal": 7900, "vatPercent": 7.5, "vatAmount": 592.5, "total": 8492.5 },
"currency": "NGN"
}
}
Response fields
| Field | Type | Description |
|---|---|---|
quoteId | string | Pass this to POST /shipments. Valid for 10 minutes, single use |
quoteExpiresAt | ISO 8601 string | When the quote expires |
tripType | string | intrastate (same state) or interstate |
serviceType | string | The service you asked for |
pickup, dropoff | object | The located address: address, city, state, lat, lng |
distanceKm | number | Driving distance between the two addresses |
packageWeightKg | number | The weight you sent |
declaredValue | number | The declared value, when you sent one |
etaRawMinutes | number | Raw driving time in minutes |
etaBufferedMinutes | number | The delivery promise in minutes (etaDays × 1,440) |
etaDays | number | Delivery time in whole days. See delivery times |
etaAt | ISO 8601 string | Estimated delivery time |
pricing | object | subtotal, vatPercent, vatAmount and total in NGN. See Pricing |
currency | string | Always NGN |
Errors
| HTTP | error.code | When |
|---|---|---|
| 400 | invalid_request | A required field is missing, serviceType is not valid, or the weight is outside the limit for the service |
| 401 | authentication_error | Missing, invalid or revoked API key |
| 422 | unserviceable | We don't deliver to that state, or don't offer door service at that address yet. See Coverage |
| 503 | service_unavailable | Address lookup or our delivery network is temporarily unavailable. Retry with backoff |
Create a shipment
Auth: API key. Creates the shipment, charges your wallet and books the pickup on our delivery network. Returns 201 Created.
Create it either with a quoteId (recommended: you are charged exactly the quoted price) or with the full address and package fields from POST /rates/estimate, in which case it is priced live at that moment.
Request headers
| Header | Required | Description |
|---|---|---|
Authorization | Yes | Bearer <API key> |
Content-Type | Yes | application/json |
Idempotency-Key | No | Any unique string, such as your order ID. Repeating a request with the same key returns the original shipment and never charges twice |
Request fields
| Field | Type | Required | Description |
|---|---|---|---|
quoteId | string | Recommended | A quote from POST /rates/estimate. Fixes the price, route, weight, service type and declared value |
pickupContactName | string | Yes | Person the rider collects from |
pickupContactPhone | string | Yes | Nigerian phone number, for example 08031234567 or +2348031234567 |
pickupContactEmail | string | Yes | Email of the pickup contact |
recipientName | string | Yes | Person receiving the package |
recipientPhone | string | Yes | Nigerian phone number of the recipient |
recipientEmail | string | Yes | Email of the recipient |
packageDescription | string | No | What is in the package. Defaults to the description on the quote |
brittleTier | string | No | How fragile the package is: low (default), medium or high |
containsLiquid | boolean | No | true if the package contains liquid. Defaults to false |
scheduledAt | ISO 8601 string | No | A future date and time for the pickup. Omit it to book the pickup straight away |
serviceType, packageWeightKg, pickupAddress, pickupCity, pickupState, dropoffAddress, dropoffCity, dropoffState | Without quoteId | Same as POST /rates/estimate. Required only when you don't send a quoteId | |
declaredValue | number | No | Without quoteId only: value of the goods in NGN for insurance. With a quoteId, the quote's declared value is used |
Example request
curl -X POST "https://api.7feastr.com/api/v1/logistics/v1/shipments" \
-H "Authorization: Bearer $SEVENFEASTR_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order_48213" \
-d '{
"quoteId": "QUOTE-9C41E07B2D5F4A18",
"pickupContactName": "Adaeze Okafor",
"pickupContactPhone": "08031234567",
"pickupContactEmail": "dispatch@adaezestores.ng",
"recipientName": "Tunde Bakare",
"recipientPhone": "+2348099876543",
"recipientEmail": "tunde.bakare@example.com",
"packageDescription": "Shoes",
"brittleTier": "low",
"containsLiquid": false
}'
// Node.js 18+ (run as an ES module, or inside an async function)
const BASE_URL = "https://api.7feastr.com/api/v1/logistics/v1";
const res = await fetch(`${BASE_URL}/shipments`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SEVENFEASTR_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": "order_48213",
},
body: JSON.stringify({
quoteId: "QUOTE-9C41E07B2D5F4A18",
pickupContactName: "Adaeze Okafor",
pickupContactPhone: "08031234567",
pickupContactEmail: "dispatch@adaezestores.ng",
recipientName: "Tunde Bakare",
recipientPhone: "+2348099876543",
recipientEmail: "tunde.bakare@example.com",
packageDescription: "Shoes",
brittleTier: "low",
containsLiquid: false,
}),
});
const body = await res.json();
if (!body.success) throw new Error(`${body.error.code}: ${body.error.message}`);
const shipment = body.data;
console.log(shipment._id, shipment.trackingCode, shipment.price);
import os
import requests
BASE_URL = "https://api.7feastr.com/api/v1/logistics/v1"
res = requests.post(
f"{BASE_URL}/shipments",
headers={
"Authorization": f"Bearer {os.environ['SEVENFEASTR_API_KEY']}",
"Idempotency-Key": "order_48213",
},
json={
"quoteId": "QUOTE-9C41E07B2D5F4A18",
"pickupContactName": "Adaeze Okafor",
"pickupContactPhone": "08031234567",
"pickupContactEmail": "dispatch@adaezestores.ng",
"recipientName": "Tunde Bakare",
"recipientPhone": "+2348099876543",
"recipientEmail": "tunde.bakare@example.com",
"packageDescription": "Shoes",
"brittleTier": "low",
"containsLiquid": False,
},
timeout=30,
)
body = res.json()
if not body["success"]:
raise RuntimeError(f"{body['error']['code']}: {body['error']['message']}")
shipment = body["data"]
print(shipment["_id"], shipment["trackingCode"], shipment["price"])
Example response
201 Created
{
"success": true,
"message": "OK",
"sandbox": true,
"data": {
"_id": "6716a3f2c9e77b0012a4d5e1",
"trackingCode": "7F-20261021-4F91A2C8",
"reference": "SHIP-9F3A1B2C4D5E6F70",
"environment": "sandbox",
"idempotencyKey": "order_48213",
"status": "pending",
"statusHistory": [{ "status": "pending", "at": "2026-10-21T09:32:10.000Z" }],
"pickupContactName": "Adaeze Okafor",
"pickupContactPhone": "08031234567",
"pickupContactEmail": "dispatch@adaezestores.ng",
"pickupAddress": "14 Allen Ave, Ikeja, Lagos, Nigeria",
"pickupCity": "Ikeja",
"pickupState": "Lagos",
"pickupLat": 6.6018,
"pickupLng": 3.3515,
"recipientName": "Tunde Bakare",
"recipientPhone": "+2348099876543",
"recipientEmail": "tunde.bakare@example.com",
"dropoffAddress": "8 Admiralty Way, Lekki Phase 1, Lagos, Nigeria",
"dropoffCity": "Lekki",
"dropoffState": "Lagos",
"dropoffLat": 6.4474,
"dropoffLng": 3.4706,
"packageWeightKg": 2,
"packageDescription": "Shoes",
"declaredValue": 45000,
"brittleTier": "low",
"containsLiquid": false,
"tripType": "intrastate",
"serviceType": "consolidated",
"deliveryType": "instant",
"distanceKm": 27.4,
"etaRawMinutes": 58,
"etaBufferedMinutes": 1440,
"etaAtOriginal": "2026-10-22T09:30:00.000Z",
"etaAt": "2026-10-22T09:30:00.000Z",
"subtotal": 7900,
"vatPercent": 7.5,
"vatAmount": 592.5,
"price": 8492.5,
"currency": "NGN",
"isCharged": true,
"isRefunded": false,
"pickupBooked": true,
"deliveryEvents": [{ "description": "Pickup booked", "at": "2026-10-21T09:32:11.000Z" }],
"proofOfDeliveryAvailable": false,
"createdAt": "2026-10-21T09:32:10.000Z",
"updatedAt": "2026-10-21T09:32:11.000Z"
}
}
Key response fields
| Field | Type | Description |
|---|---|---|
_id | string | The shipment ID. Use it in /shipments/:id calls |
trackingCode | string | Format 7F-YYYYMMDD-XXXXXXXX. Share it with your recipient for public tracking |
reference | string | 7feastr's business reference for the transaction |
status | string | Starts as pending. See statuses |
deliveryType | string | instant, or scheduled when you sent scheduledAt |
subtotal, vatPercent, vatAmount | number | The price breakdown, identical to the quote |
price | number | The total charged to your wallet, identical to pricing.total on the quote |
pickupBooked | boolean | true once the pickup is confirmed on our delivery network |
deliveryEvents | array | Delivery progress as { description, at }, newest first |
proofOfDeliveryAvailable | boolean | true when a delivery photo or signature can be downloaded |
Errors
| HTTP | error.code | When |
|---|---|---|
| 400 | invalid_request | A required field is missing or invalid, the quote has expired or was already used ("This rate quote has expired or is invalid."), or scheduledAt is not in the future |
| 401 | authentication_error | Missing, invalid or revoked API key |
| 402 | insufficient_funds | Your wallet balance is lower than the total. Nothing is created. Fund your wallet and retry with the same quoteId while it is still valid |
| 403 | permission_error | Production is not enabled for your account yet |
| 422 | unserviceable | Without quoteId: we don't deliver to that state or address yet |
| 503 | service_unavailable | Our delivery network is temporarily unavailable. Retry with backoff and the same Idempotency-Key, requesting a new quote if yours has expired |
Idempotency
Network timeouts happen. To retry POST /shipments safely, send an Idempotency-Key header with a value that is unique to the order, such as your own order ID:
Idempotency-Key: order_48213
If a request with the same key has already created a shipment in the same environment, the API returns that original shipment instead of creating a new one, and your wallet is not charged again. Keys are scoped to your account and environment.
Scheduled pickups
Send scheduledAt as a future ISO 8601 date, for example "2026-10-23T08:00:00.000Z", to have the shipment picked up at that time. The shipment is created and charged straight away, with deliveryType: "scheduled". A date in the past returns 400 invalid_request.
List shipments
Auth: API key. Returns your shipments in the key's environment, newest first.
| Query parameter | Type | Required | Description |
|---|---|---|---|
page | integer | No | Page number, from 1. Default 1 |
limit | integer | No | Results per page, 1 to 100. Default 20 |
status | string | No | One of pending, assigned, picked_up, in_transit, delivered, failed, cancelled |
from | ISO 8601 string | No | Only shipments created at or after this time |
to | ISO 8601 string | No | Only shipments created at or before this time |
curl "https://api.7feastr.com/api/v1/logistics/v1/shipments?status=in_transit&page=1&limit=20" \
-H "Authorization: Bearer $SEVENFEASTR_API_KEY"
200 OK. data is an array of shipment objects like the one above, and meta describes the page:
{
"success": true,
"message": "OK",
"sandbox": true,
"data": [
{
"_id": "6716a3f2c9e77b0012a4d5e1",
"trackingCode": "7F-20261021-4F91A2C8",
"status": "in_transit",
"price": 8492.5,
"currency": "NGN",
"pickupBooked": true,
"proofOfDeliveryAvailable": false,
"createdAt": "2026-10-21T09:32:10.000Z"
}
],
"meta": { "page": 1, "limit": 20, "total": 1, "totalPages": 1 }
}
The list item above is abridged. Each item has the same fields as GET /shipments/:id.
Errors: 400 invalid_request for an unknown status or an invalid from / to date, and 401 authentication_error.
Get a shipment
Auth: API key. Returns one shipment, with the same fields as the create response. Use the _id from the create response as :id.
curl "https://api.7feastr.com/api/v1/logistics/v1/shipments/6716a3f2c9e77b0012a4d5e1" \
-H "Authorization: Bearer $SEVENFEASTR_API_KEY"
Errors: 404 not_found when the shipment doesn't exist, belongs to another merchant or to the other environment, and 401 authentication_error.
To follow a shipment after it is created, see Tracking.