Quotes and creating shipments
Last updated July 30, 2026
Creating a shipment is a two-step flow: get a rate quote, then confirm it. This guarantees the price you show your customer at checkout is exactly the price you're charged, even if rates change between the two calls.
Step 1: Request a rate quote
POST /rates/estimate
| Field | Type | Required | Notes |
|---|---|---|---|
pickupAddress | string | yes | Street-level address |
pickupCity | string | yes | |
pickupState | string | yes | Must be a real Nigerian state |
dropoffAddress | string | yes | |
dropoffCity | string | yes | |
dropoffState | string | yes | |
packageWeightKg | number | yes | Weight in kilograms. Between 0.01 and 500 |
Every address is geocoded and validated to confirm it resolves to a real, deliverable location inside Nigeria. Addresses that can't be located, or resolve outside Nigeria, are rejected with invalid_request.
Response:
{
"success": true,
"sandbox": true,
"data": {
"quoteId": "QUOTE-A1B2C3D4E5F6",
"quoteExpiresAt": "2026-07-30T14:32:00.000Z",
"tripType": "interstate",
"pickup": { "address": "12 Admiralty Way, Lekki, Lagos", "city": "Lekki", "state": "Lagos", "lat": 6.4406, "lng": 3.4739 },
"dropoff": { "address": "5 Ademola Adetokunbo St, Wuse 2, Abuja", "city": "Wuse 2", "state": "Abuja", "lat": 9.0765, "lng": 7.3986 },
"distanceKm": 462.3,
"packageWeightKg": 2.5,
"etaRawMinutes": 340,
"etaBufferedMinutes": 370,
"etaAt": "2026-07-30T22:10:00.000Z",
"pricing": { "subtotal": 12500, "vatPercent": 7.5, "vatAmount": 937.5, "total": 13437.5 },
"currency": "NGN"
}
}
tripType is determined automatically by comparing the pickup and dropoff states: intrastate when they match, interstate otherwise.
A quote is valid for 10 minutes. Show pricing.total and etaAt to your customer for confirmation before proceeding to step 2.
Step 2: Confirm and create the shipment
POST /shipments
Reference the quote from step 1 with quoteId, and supply the remaining shipment details:
| Field | Type | Required | Notes |
|---|---|---|---|
quoteId | string | recommended | Locks in the exact quoted price, distance, and ETA. If omitted, the shipment is priced fresh at creation time using the same address/weight fields as /rates/estimate |
pickupContactName | string | yes | |
pickupContactPhone | string | yes | |
pickupContactEmail | string | yes | |
recipientName | string | yes | |
recipientPhone | string | yes | |
recipientEmail | string | yes | |
packageDescription | string | no | |
brittleTier | low | medium | high | no | Defaults to low. Affects how the package is handled in transit |
containsLiquid | boolean | no | Defaults to false |
scheduledAt | ISO 8601 date string | no | If supplied and in the future, the shipment is scheduled for that time. Otherwise it's dispatched instantly |
If you don't pass quoteId, the same pickupAddress/pickupCity/pickupState/dropoffAddress/dropoffCity/dropoffState/packageWeightKg fields from step 1 are required directly on this call instead.
Idempotency
Pass an Idempotency-Key header (any unique string you generate, such as your own order ID) on POST /shipments. If the request is retried with the same key, the original shipment is returned instead of creating a duplicate, and your wallet is not double-charged. This protects against network retries and duplicate submissions.
Idempotency-Key: order_48213
Response
{
"success": true,
"sandbox": true,
"data": {
"_id": "6890...",
"trackingCode": "7F-20260730-4F91A2C8",
"reference": "SHIP-9F3A1B2C4D5E",
"status": "pending",
"tripType": "interstate",
"deliveryType": "instant",
"environment": "sandbox",
"subtotal": 12500,
"vatPercent": 7.5,
"vatAmount": 937.5,
"price": 13437.5,
"currency": "NGN",
"packageWeightKg": 2.5,
"brittleTier": "low",
"containsLiquid": false,
"statusHistory": [{ "status": "pending", "at": "2026-07-30T14:22:00.000Z" }],
"createdAt": "2026-07-30T14:22:00.000Z"
}
}
Every shipment has both a trackingCode (share this with your recipient for public tracking) and a reference (your internal-facing business reference for the transaction).
Weight and pricing, in detail
Weight is always expressed in kilograms, as a decimal. Price is calculated as:
subtotal = baseFee + (ratePerKm × distanceKm) + weightTierSurcharge
total = subtotal + (subtotal × vatPercent / 100)
baseFee, ratePerKm, the weight-tier surcharge table, and vatPercent are all configured by 7feastr and can vary by region, they are never hardcoded on your side. Always read the price from the API response rather than estimating it yourself.