Skip to documentation
Bento Delivaz / Developers

Integration reference

Partner API

Quote, dispatch and track your deliveries with Bento.

Overview

Quote, book and track parcel deliveries from your backend. Authenticate API requests with a credential issued by Bento.

Choose the base URL for your environment. Endpoint paths such as /quotes are appended to this base. Examples use sample data. Replace locations and amounts with values from your sandbox configuration and quote.

EnvironmentBase URL
Sandboxhttps://api.bentodelivaz.com/sandbox/v1
Productionhttps://api.bentodelivaz.com/prod/v1
Local testinghttp://bentoadmin.test/api/partner/v1

Amounts use integer kobo: 100000 represents NGN 1,000. Prices come from your approved tariff. Coordinates are decimal latitude/longitude; timestamps use RFC 3339 UTC. Pickup and destination must be within your configured service coverage.

Authentication

Send Authorization: Bearer <issued-key> and Content-Type: application/json. Keep the key on your backend, never in a browser or customer app. API keys and webhook signing secrets are separate. Sandbox and production credentials are not interchangeable.

Each endpoint lists its required scope. Keys can expire, be revoked or be rotated. Suspending bookings can leave existing delivery lookup and tracking available. No credential is required to read this documentation.

Quick start

  1. Obtain sandbox access from Bento. Isolation validation, basic parcel-service configuration and a technical contact are required; production contract/UAT/finance approvals are not sandbox gates.
  2. Request a quote. Bento calculates the route distance and applies your tariff. Quotes expire after 10 minutes; routing failures do not fall back to straight-line pricing.
  3. Book with the quote ID, a unique external reference, contacts and the unchanged shipment. Keep the same Idempotency-Key (8–128 characters) and body when retrying a timed-out request.
  4. Signal /ready only after the vendor has packaged the parcel. Booking alone does not dispatch a rider. Repeated readiness calls do not start another dispatch.
  5. Track signed callbacks or retrieve the delivery snapshot. Pickup exposes in_transit with picked_up_at. Delivery requires recipient OTP; a photo is optional.

Set BENTO_BASE_URL to one of the complete base URLs above (no trailing slash), and BENTO_API_KEY to your server-side credential. For example, sandbox quoting uses https://api.bentodelivaz.com/sandbox/v1/quotes. Run the example requests from your development environment.

API v1 · 5 operations

Endpoint reference

Append these paths to your selected environment base URL.

POST

/quotes

#

Calculate a server-routed expiring B2B quote

Required scope: quotes:create.

Request body fields
FieldTypeRequiredDetails
shipmentobjectYes—
shipment.pickupobjectYes—
shipment.pickup.addressstringYesmaxLength: 500.
shipment.pickup.latitudenumberYesminimum: -90. maximum: 90.
shipment.pickup.longitudenumberYesminimum: -180. maximum: 180.
shipment.pickup.namestringNomaxLength: 120.
shipment.pickup.phonestringNomaxLength: 32.
shipment.recipientobjectYes—
shipment.recipient.addressstringYesmaxLength: 500.
shipment.recipient.latitudenumberYesminimum: -90. maximum: 90.
shipment.recipient.longitudenumberYesminimum: -180. maximum: 180.
shipment.recipient.namestringNomaxLength: 120.
shipment.recipient.phonestringNomaxLength: 32.
shipment.parcelobjectYes—
shipment.parcel.category_idintegerNominimum: 1.
shipment.parcel.weight_kgnumberNo—
shipment.parcel.length_cmnumberNo—
shipment.parcel.width_cmnumberNo—
shipment.parcel.height_cmnumberNo—
shipment.parcel.instructionsstring | nullNomaxLength: 1000.

Example request cURL

curl --request POST "$BENTO_BASE_URL/quotes" \
  --header "Authorization: Bearer $BENTO_API_KEY" \
  --header 'Content-Type: application/json' \
  --data '{
    "shipment": {
        "pickup": {
            "address": "Example pickup address",
            "latitude": 9.0765,
            "longitude": 7.3986,
            "name": "Example sender",
            "phone": "+2340000000000"
        },
        "recipient": {
            "address": "Example delivery address",
            "latitude": 9.065,
            "longitude": 7.42,
            "name": "Example recipient",
            "phone": "+2340000000001"
        },
        "parcel": {
            "instructions": "Example shipment; replace with eligible test data"
        }
    }
}'

Example response 201 · JSON

{
    "id": "00000000-0000-4000-8000-000000000001",
    "distance_metres": 4200,
    "amount_kobo": 100000,
    "currency": "NGN",
    "tariff_version": "example-v1",
    "breakdown": [
        {
            "name": "delivery",
            "amount_kobo": 100000
        }
    ],
    "expires_at": "2030-01-01T10:10:00Z"
}
Response fields
FieldTypeDetails
idstringformat: uuid.
distance_metresintegerminimum: 0.
amount_kobointegerminimum: 0.
currencystringValue: NGN.
tariff_versionstring—
breakdownarray—
expires_atstringformat: date-time.
POST

/deliveries

#

Book once from a quote; wait for readiness

Required scope: deliveries:create.

Parameters

NameLocationRequiredDetails
Idempotency-KeyheaderYesSame key and same body returns original creation response; changed body conflicts.
Request body fields
FieldTypeRequiredDetails
quote_idstringYesformat: uuid.
external_referencestringYesmaxLength: 128.
shipmentobjectYes—
shipment.pickupobjectYes—
shipment.pickup.addressstringYesmaxLength: 500.
shipment.pickup.latitudenumberYesminimum: -90. maximum: 90.
shipment.pickup.longitudenumberYesminimum: -180. maximum: 180.
shipment.pickup.namestringYesmaxLength: 120.
shipment.pickup.phonestringYesmaxLength: 32.
shipment.recipientobjectYes—
shipment.recipient.addressstringYesmaxLength: 500.
shipment.recipient.latitudenumberYesminimum: -90. maximum: 90.
shipment.recipient.longitudenumberYesminimum: -180. maximum: 180.
shipment.recipient.namestringYesmaxLength: 120.
shipment.recipient.phonestringYesmaxLength: 32.
shipment.parcelobjectYes—
shipment.parcel.category_idintegerNominimum: 1.
shipment.parcel.weight_kgnumberNo—
shipment.parcel.length_cmnumberNo—
shipment.parcel.width_cmnumberNo—
shipment.parcel.height_cmnumberNo—
shipment.parcel.instructionsstring | nullNomaxLength: 1000.

Example request cURL

curl --request POST "$BENTO_BASE_URL/deliveries" \
  --header "Authorization: Bearer $BENTO_API_KEY" \
  --header 'Content-Type: application/json' \
  --header 'Idempotency-Key: partner-booking-001' \
  --data '{
    "quote_id": "00000000-0000-4000-8000-000000000001",
    "external_reference": "PARTNER-ORDER-001",
    "shipment": {
        "pickup": {
            "address": "Example pickup address",
            "latitude": 9.0765,
            "longitude": 7.3986,
            "name": "Example sender",
            "phone": "+2340000000000"
        },
        "recipient": {
            "address": "Example delivery address",
            "latitude": 9.065,
            "longitude": 7.42,
            "name": "Example recipient",
            "phone": "+2340000000001"
        },
        "parcel": {
            "instructions": "Example shipment; replace with eligible test data"
        }
    }
}'

Example response 201 · JSON

{
    "id": "00000000-0000-4000-8000-000000000002",
    "external_reference": "PARTNER-ORDER-001",
    "status": "accepted",
    "readiness_status": "awaiting_ready",
    "amount_kobo": 100000,
    "currency": "NGN",
    "tariff_version": "example-v1",
    "version": 1,
    "created_at": "2030-01-01T10:00:00Z",
    "ready_at": null,
    "assigned_at": null,
    "picked_up_at": null,
    "delivered_at": null,
    "canceled_at": null,
    "failure_reason": null,
    "proof_of_delivery": null,
    "lookup_url": "https://api.bentodelivaz.com/sandbox/v1/deliveries/00000000-0000-4000-8000-000000000002"
}
Response fields
FieldTypeDetails
idstringformat: uuid.
external_referencestring—
statusstringValues: accepted, assigned, in_transit, delivered, canceled, dispatch_failed.
readiness_statusstringValues: awaiting_ready, ready.
amount_kobointeger—
currencystringValue: NGN.
tariff_versionstring—
versioninteger—
created_atstring | nullformat: date-time.
ready_atstring | nullformat: date-time.
assigned_atstring | nullformat: date-time.
picked_up_atstring | nullformat: date-time.
delivered_atstring | nullformat: date-time.
canceled_atstring | nullformat: date-time.
failure_reasonstring | null—
proof_of_deliveryobject | null—
proof_of_delivery.methodstringValue: otp.
proof_of_delivery.verifiedboolean—
proof_of_delivery.verified_atstringformat: date-time.
proof_of_delivery.photo_availableboolean—
lookup_urlstring—
POST

/deliveries/{id}/ready

#

Signal vendor readiness and initiate dispatch idempotently

Required scope: deliveries:ready.

Parameters

NameLocationRequiredDetails
idpathYesPublic delivery UUID owned by this partner.

Example request cURL

curl --request POST "$BENTO_BASE_URL/deliveries/$DELIVERY_ID/ready" \
  --header "Authorization: Bearer $BENTO_API_KEY" \
  --header 'Content-Type: application/json'

Example response 200 · JSON

{
    "id": "00000000-0000-4000-8000-000000000002",
    "external_reference": "PARTNER-ORDER-001",
    "status": "accepted",
    "readiness_status": "ready",
    "amount_kobo": 100000,
    "currency": "NGN",
    "tariff_version": "example-v1",
    "version": 2,
    "created_at": "2030-01-01T10:00:00Z",
    "ready_at": "2030-01-01T10:02:00Z",
    "assigned_at": null,
    "picked_up_at": null,
    "delivered_at": null,
    "canceled_at": null,
    "failure_reason": null,
    "proof_of_delivery": null,
    "lookup_url": "https://api.bentodelivaz.com/sandbox/v1/deliveries/00000000-0000-4000-8000-000000000002"
}
Response fields
FieldTypeDetails
idstringformat: uuid.
external_referencestring—
statusstringValues: accepted, assigned, in_transit, delivered, canceled, dispatch_failed.
readiness_statusstringValues: awaiting_ready, ready.
amount_kobointeger—
currencystringValue: NGN.
tariff_versionstring—
versioninteger—
created_atstring | nullformat: date-time.
ready_atstring | nullformat: date-time.
assigned_atstring | nullformat: date-time.
picked_up_atstring | nullformat: date-time.
delivered_atstring | nullformat: date-time.
canceled_atstring | nullformat: date-time.
failure_reasonstring | null—
proof_of_deliveryobject | null—
proof_of_delivery.methodstringValue: otp.
proof_of_delivery.verifiedboolean—
proof_of_delivery.verified_atstringformat: date-time.
proof_of_delivery.photo_availableboolean—
lookup_urlstring—
GET

/deliveries/{id}

#

Retrieve the current owned delivery snapshot

Required scope: deliveries:read.

Parameters

NameLocationRequiredDetails
idpathYesPublic delivery UUID owned by this partner.

Example request cURL

curl --request GET "$BENTO_BASE_URL/deliveries/$DELIVERY_ID" \
  --header "Authorization: Bearer $BENTO_API_KEY"

Example response 200 · JSON

{
    "id": "00000000-0000-4000-8000-000000000002",
    "external_reference": "PARTNER-ORDER-001",
    "status": "in_transit",
    "readiness_status": "ready",
    "amount_kobo": 100000,
    "currency": "NGN",
    "tariff_version": "example-v1",
    "version": 4,
    "created_at": "2030-01-01T10:00:00Z",
    "ready_at": "2030-01-01T10:02:00Z",
    "assigned_at": "2030-01-01T10:04:00Z",
    "picked_up_at": "2030-01-01T10:15:00Z",
    "delivered_at": null,
    "canceled_at": null,
    "failure_reason": null,
    "proof_of_delivery": null,
    "lookup_url": "https://api.bentodelivaz.com/sandbox/v1/deliveries/00000000-0000-4000-8000-000000000002"
}
Response fields
FieldTypeDetails
idstringformat: uuid.
external_referencestring—
statusstringValues: accepted, assigned, in_transit, delivered, canceled, dispatch_failed.
readiness_statusstringValues: awaiting_ready, ready.
amount_kobointeger—
currencystringValue: NGN.
tariff_versionstring—
versioninteger—
created_atstring | nullformat: date-time.
ready_atstring | nullformat: date-time.
assigned_atstring | nullformat: date-time.
picked_up_atstring | nullformat: date-time.
delivered_atstring | nullformat: date-time.
canceled_atstring | nullformat: date-time.
failure_reasonstring | null—
proof_of_deliveryobject | null—
proof_of_delivery.methodstringValue: otp.
proof_of_delivery.verifiedboolean—
proof_of_delivery.verified_atstringformat: date-time.
proof_of_delivery.photo_availableboolean—
lookup_urlstring—
GET

/deliveries/{id}/location

#

Retrieve the current assigned rider last-reported position

Required scope: location:read.

Parameters

NameLocationRequiredDetails
idpathYesPublic delivery UUID owned by this partner.

Example request cURL

curl --request GET "$BENTO_BASE_URL/deliveries/$DELIVERY_ID/location" \
  --header "Authorization: Bearer $BENTO_API_KEY"

Example response 200 · JSON

{
    "delivery_id": "00000000-0000-4000-8000-000000000002",
    "availability": "available",
    "location": {
        "latitude": 9.07,
        "longitude": 7.41,
        "observed_at": "2030-01-01T10:16:00Z"
    },
    "as_of": "2030-01-01T10:16:10Z",
    "age_seconds": 10,
    "is_stale": false,
    "reason": null
}
Response fields
FieldTypeDetails
delivery_idstringformat: uuid.
availabilitystringValues: available, not_available, tracking_ended.
locationobject | null—
location.latitudenumber—
location.longitudenumber—
location.observed_atstringformat: date-time.
as_ofstringformat: date-time.
age_secondsinteger | null—
is_staleboolean—
reasonstring | null—

Status and tracking

Statuses are accepted, assigned, in_transit, delivered, canceled and dispatch_failed. Readiness is separate: awaiting_ready or ready. Bookings are accepted before a rider is assigned. Use the lookup response's version and timestamps for reconciliation.

A rider rejection can return a delivery to accepted for reassignment; it does not necessarily cancel the shipment. When assignment cannot complete before the configured deadline, dispatch_failed is reported. Cancellation, return and redelivery arrangements follow your agreed service policy.

Location is available only for the current assigned rider during active execution. The response includes observed_at, age_seconds, is_stale and availability. Show stale or unavailable reports honestly. Tracking ends on delivery, cancellation or dispatch failure; a former rider's location is not exposed after reassignment. Keep your partner key on your server and authorize your own customer's access to any map.

Polling and freshness limits are set during onboarding. Suggested starting values are 15-second polling and 60-second staleness, subject to validation. Respect rate limits and Retry-After.

Webhooks

Bento posts signed JSON to your provisioned HTTPS receiver. Callback delivery is at least once: duplicates and out-of-order events are possible. Deduplicate by event_id, compare per-delivery version, and use lookup after gaps. Persist an accepted event before returning any 2xx response.

Header Meaning
X-Bento-Event-Id Stable event identifier
X-Bento-Timestamp Unix seconds at signing time; refreshed on retry
X-Bento-Key-Id Select the separately supplied verification secret
X-Bento-Signature sha256= plus the hexadecimal HMAC-SHA256 signature

Sign/verify timestamp + '.' + exact raw request body. Use constant-time comparison and a bounded timestamp tolerance, for example five minutes. Verify the raw bytes before JSON parsing; do not reserialize parsed JSON to reconstruct a body.

Events include delivery.accepted, delivery.ready, delivery.assigned, delivery.in_transit, delivery.delivered, delivery.canceled and delivery.dispatch_failed. Onboarding sends a signed connection_test with event_id, occurred_at and test=true; acknowledge it without creating a shipment or expecting delivery/version fields.

Sandbox quote/booking tests may start without a callback. Status events remain queued without spending retries until the URL, signing secret and validation are ready. After validation, older queued milestones may arrive; their original IDs, occurrence times and versions remain unchanged. Failed callbacks use bounded retries; Bento can replay exhausted events. Agree secret rotation and install a staged replacement before Bento switches signing keys.

Download the Node.js verification example below. Raw OTPs are not part of partner tracking responses or webhook payloads.

Errors and retries

Errors have the shape:

{
  "error": {
    "code": "quote_expired",
    "message": "Quote expired",
    "request_id": "example-request-id"
  }
}
HTTP status Action
400 Use the approved HTTPS API endpoint.
401 Check credential expiry/revocation and environment.
403 Check endpoint scope or booking suspension.
404 Resource missing or not owned by your partner.
409 Resolve an idempotency/reference conflict or invalid transition; do not create a second booking to bypass it.
422 Correct shipment data or obtain a fresh valid quote.
429 Wait for Retry-After and reduce request frequency.
503 Temporary dependency/configuration failure; preserve booking key/body when retrying.

Quote expiry, changed shipment inputs and a superseded unused tariff can require a new quote. Accepted booking prices remain fixed. Replaying an identical creation request returns its original response, even after quote expiry; use lookup for the current state.

The JSON error request_id and response X-Request-Id support troubleshooting. Share identifiers with your technical contact, never secret values or recipient OTPs.

Integration toolkit

Downloads

Import the collection, inspect the schemas or add signature verification to your receiver.