## Overview Quote, book and track parcel deliveries from your backend. Authenticate API requests with a credential issued by Bento. **Sandbox:** `https://api.bentodelivaz.com/sandbox/v1` **Production:** `https://api.bentodelivaz.com/prod/v1` **Local testing:** `http://bentoadmin.test/api/partner/v1` Append endpoint paths such as `/quotes` to the selected base URL. Examples use sample data. Replace locations and amounts with values from your sandbox configuration and quote. 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 ` 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 the complete environment base URL listed above, without a trailing slash, and `BENTO_API_KEY` to your server-side credential. Run the example requests from your development environment. ## 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: ```json { "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.