POST /v1/bulk-deliveries/{ref}/quote
POST /v1/bulk-deliveries/{ref}/quote
| Scope | Idempotency-Key | Operation ID |
|---|---|---|
quotes.create | Optional | quoteBulkDelivery |
Quote every confirmed child; the total is their sum.
(Re)quotes every active child: each from the shared pickup to its own
confirmed drop-off, with its own vehicle and service, by the same Quotation service as a single
delivery, for the Bulk's partner. The total is the plain sum of the child quotes: no Bulk
price, distance or discount. Allowed before the sender confirms, once every active recipient
has confirmed their location (else BULK_RECIPIENTS_PENDING); after confirmation
BULK_STATE_CHANGED, once cancelled BULK_CANCELLED. Routing runs outside any lock; a child
whose drop-off changed meanwhile is RETRYABLE. A failed quote leaves the Bulk as it was. The
quote revision moves on with every quote: confirm names the revision reviewed. The tenant is
sent bulk.quote_ready. Idempotency-Key optional (a repeat quotes again). Audited (bulk.quote).
Request
Signed with your API credential: see Authentication.
| Parameter | In | Required | Description |
|---|---|---|---|
Idempotency-Key | header | No | One key per user intent, reused for every retry of that intent. See Idempotency. |
ref | path | Yes | Pattern ^BK-[0-9A-HJKMNP-TV-Z]{8}$. |
Responses
200
Quoted; the total and the new revision.
| Field | Type | Required | Description |
|---|---|---|---|
bulk_reference | string | Yes | Pattern ^BK-[0-9A-HJKMNP-TV-Z]{8}$. |
status | string | Yes | One of: BULK_DRAFT, BULK_AWAITING_RECIPIENTS, BULK_PARTIALLY_CONFIRMED, BULK_READY_FOR_QUOTE, BULK_AWAITING_SENDER_CONFIRMATION, BULK_DISPATCHING, BULK_IN_PROGRESS, BULK_PARTIALLY_COMPLETED, BULK_COMPLETED, BULK_CANCELLED, BULK_EXCEPTION. |
recipient_count | integer | Yes | 2–5. |
active_recipient_count | integer | Yes | 0–5. |
confirmed_count | integer | Yes | 0–5. |
quote_revision | integer | Yes | |
total | string or null | Yes | The sum of the children's quotes, null until every active child is quoted. |
currency | string | Yes | One of: GHS. |
children | array of object | Yes | |
children[].position | integer | Yes | 1–5. |
children[].delivery_reference | string | Yes | Public reference of a delivery job or Bulk child. Pattern ^DX-[0-9A-HJKMNP-TV-Z]{8}$. |
children[].status | string | Yes | The child request's status (request-level states; REMOVED once removed) |
children[].location_confirmed | boolean | Yes | The recipient confirmed the drop-off |
children[].recipient | object | Yes | |
children[].recipient.first_name | string | Yes | |
children[].recipient.last_name | string | Yes | |
children[].recipient.phone | string | Yes | Masked: the last three digits only |
children[].link_expires_at | string or null, date-time | Yes | When the child's live link stops working; null once revoked |
children[].resends_remaining | integer or null | Yes | Resends left while the recipient is awaited, else null |
children[].resend_available_at | string or null, date-time | Yes | When the resend cooldown ends while the recipient is awaited, else null |
children[].quote | object or null | Yes | The child's own quote: the server's total only, never its arithmetic. expired is judged by the server clock. Null before the child is quoted. |
children[].quote.reference | string | Yes | Pattern ^QT-[0-9A-HJKMNP-TV-Z]{8}$. |
children[].quote.total | string | Yes | Exact decimal with two places; never a JSON number. Pattern ^-?[0-9]+\.[0-9]{2}$. |
children[].quote.currency | string | Yes | One of: GHS. |
children[].quote.expires_at | string, date-time | Yes | |
children[].quote.expired | boolean | Yes | |
children[].customer_status | string or null | Yes | The child's delivery status as tracking shows it, once dispatched |
children[].tracking_links | object | No | The child's tracking links, shown once on the tenant's confirming response: the sender's link confirms the handover |
children[].tracking_links.sender_url | string | Yes | |
children[].tracking_links.recipient_url | string | Yes | |
children[].tracking_links.expires_at | string, date-time | Yes | |
cancellations | array of object | No | After dispatch, each child's answer to the cancellation; present only on that answer. |
cancellations[].delivery_reference | string | Yes | Public reference of a delivery job or Bulk child. Pattern ^DX-[0-9A-HJKMNP-TV-Z]{8}$. |
cancellations[].cancelled | boolean | Yes | |
cancellations[].reason | string or null | Yes | Why the stage policy left it, e.g. after_pickup |
created_at | string, date-time | Yes |
400
Structural validation failed or an Idempotency-Key is missing.
The error envelope: code, message, detail, request_id.
401
Unknown, revoked or wrong-environment credential, bad signature, or expired session.
The error envelope: code, message, detail, request_id.
403
Tenant access denied (existence is not disclosed), insufficient scope, MFA/step-up required, partner blocked or module disabled.
The error envelope: code, message, detail, request_id.
409
State changed, race lost, or idempotency conflict. Refetch before deciding again.
The error envelope: code, message, detail, request_id.
422
Semantically invalid (location, vehicle, server-controlled field, bulk limits).
The error envelope: code, message, detail, request_id.
429
Rate limit reached; honour Retry-After.
The error envelope: code, message, detail, request_id.
500
Unexpected failure. Only code and request_id are returned.
The error envelope: code, message, detail, request_id.
503
Pricing/routing unavailable, request timeout, retryable contention, or an operator must act first (ACCOUNTING_PERIOD_UNAVAILABLE, outcome REQUIRES_ADMIN), or the payment provider cannot start a payment (PAYMENT_PROVIDER_UNAVAILABLE, outcome REQUIRES_PAYMENT; nothing charged).
The error envelope: code, message, detail, request_id.