Skip to main content

POST /v1/bulk-deliveries

POST /v1/bulk-deliveries

ScopeIdempotency-KeyOperation ID
delivery.createRequiredcreateBulkDelivery

Create a Bulk Delivery, one parent with two to five children.

One pickup, one sender, one vehicle and two to five recipients, each becoming an ordinary delivery request with its own DX- reference and its own secure link, sent by SMS once the creation commits. Each recipient supplies their own drop-off: there is no destination field. A count outside the effective limits is BULK_RECIPIENT_LIMIT and nothing is created; a phone already among the recipients is VALIDATION_FAILED with detail.reason: duplicate_phone unless the tenant allows duplicates. The module off, for the platform or the tenant, is MODULE_DISABLED. The partner must be available to the tenant (NO_ELIGIBLE_PARTNER, PARTNER_BLOCKED); the pickup pin follows the outcomes; the vehicle must be offered for the pickup (VEHICLE_UNAVAILABLE). Nothing is priced or dispatched. Idempotency-Key required: a repeated key replays the parent with 200. Audited (bulk.create).

Request​

Signed with your API credential: see Authentication.

ParameterInRequiredDescription
Idempotency-KeyheaderYesOne key per user intent, reused for every retry of that intent. See Idempotency.

Body​

FieldTypeRequiredDescription
partnerstringYesA partner reference available to the tenant (GET /v1/partners)
senderobjectYes
sender.namestringYesAt most 100 characters.
sender.phonestringYesAt most 32 characters.
pickupobjectYes
pickup.pinobjectYes
pickup.pin.latitudenumberYesWGS84; precision is judged as sent
pickup.pin.longitudenumberYes
pickup.pin.accuracy_mnumberNo
pickup.ghanapost_referencestringNo
pickup.address_descriptionstringNo
pickup.landmarkstringNo
vehicle_typestringYesA catalogue code the partner offers for the pickup
service_typestringNoSTANDARD when absent One of: EXPRESS, PRIORITY, STANDARD, NEXT_DAY.
notestringNoAt most 280 characters.
recipientsarray of objectYes
recipients[].first_namestringYesAt most 100 characters.
recipients[].last_namestringYesAt most 100 characters.
recipients[].phonestringYesNormalised to E.164 by the server At most 32 characters.
recipients[].emailstringNoAt most 254 characters.

Responses​

200​

An earlier creation with this key, replayed; nothing changed.

FieldTypeRequiredDescription
bulk_referencestringYesPattern ^BK-[0-9A-HJKMNP-TV-Z]{8}$.
statusstringYesOne 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_countintegerYes2–5.
active_recipient_countintegerYes0–5.
confirmed_countintegerYes0–5.
quote_revisionintegerYes
totalstring or nullYesThe sum of the children's quotes, null until every active child is quoted.
currencystringYesOne of: GHS.
childrenarray of objectYes
children[].positionintegerYes1–5.
children[].delivery_referencestringYesPublic reference of a delivery job or Bulk child. Pattern ^DX-[0-9A-HJKMNP-TV-Z]{8}$.
children[].statusstringYesThe child request's status (request-level states; REMOVED once removed)
children[].location_confirmedbooleanYesThe recipient confirmed the drop-off
children[].recipientobjectYes
children[].recipient.first_namestringYes
children[].recipient.last_namestringYes
children[].recipient.phonestringYesMasked: the last three digits only
children[].link_expires_atstring or null, date-timeYesWhen the child's live link stops working; null once revoked
children[].resends_remaininginteger or nullYesResends left while the recipient is awaited, else null
children[].resend_available_atstring or null, date-timeYesWhen the resend cooldown ends while the recipient is awaited, else null
children[].quoteobject or nullYesThe 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.referencestringYesPattern ^QT-[0-9A-HJKMNP-TV-Z]{8}$.
children[].quote.totalstringYesExact decimal with two places; never a JSON number. Pattern ^-?[0-9]+\.[0-9]{2}$.
children[].quote.currencystringYesOne of: GHS.
children[].quote.expires_atstring, date-timeYes
children[].quote.expiredbooleanYes
children[].customer_statusstring or nullYesThe child's delivery status as tracking shows it, once dispatched
children[].tracking_linksobjectNoThe child's tracking links, shown once on the tenant's confirming response: the sender's link confirms the handover
children[].tracking_links.sender_urlstringYes
children[].tracking_links.recipient_urlstringYes
children[].tracking_links.expires_atstring, date-timeYes
cancellationsarray of objectNoAfter dispatch, each child's answer to the cancellation; present only on that answer.
cancellations[].delivery_referencestringYesPublic reference of a delivery job or Bulk child. Pattern ^DX-[0-9A-HJKMNP-TV-Z]{8}$.
cancellations[].cancelledbooleanYes
cancellations[].reasonstring or nullYesWhy the stage policy left it, e.g. after_pickup
created_atstring, date-timeYes

201​

Created; one link SMS is owed per child.

FieldTypeRequiredDescription
bulk_referencestringYesPattern ^BK-[0-9A-HJKMNP-TV-Z]{8}$.
statusstringYesOne 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_countintegerYes2–5.
active_recipient_countintegerYes0–5.
confirmed_countintegerYes0–5.
quote_revisionintegerYes
totalstring or nullYesThe sum of the children's quotes, null until every active child is quoted.
currencystringYesOne of: GHS.
childrenarray of objectYes
children[].positionintegerYes1–5.
children[].delivery_referencestringYesPublic reference of a delivery job or Bulk child. Pattern ^DX-[0-9A-HJKMNP-TV-Z]{8}$.
children[].statusstringYesThe child request's status (request-level states; REMOVED once removed)
children[].location_confirmedbooleanYesThe recipient confirmed the drop-off
children[].recipientobjectYes
children[].recipient.first_namestringYes
children[].recipient.last_namestringYes
children[].recipient.phonestringYesMasked: the last three digits only
children[].link_expires_atstring or null, date-timeYesWhen the child's live link stops working; null once revoked
children[].resends_remaininginteger or nullYesResends left while the recipient is awaited, else null
children[].resend_available_atstring or null, date-timeYesWhen the resend cooldown ends while the recipient is awaited, else null
children[].quoteobject or nullYesThe 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.referencestringYesPattern ^QT-[0-9A-HJKMNP-TV-Z]{8}$.
children[].quote.totalstringYesExact decimal with two places; never a JSON number. Pattern ^-?[0-9]+\.[0-9]{2}$.
children[].quote.currencystringYesOne of: GHS.
children[].quote.expires_atstring, date-timeYes
children[].quote.expiredbooleanYes
children[].customer_statusstring or nullYesThe child's delivery status as tracking shows it, once dispatched
children[].tracking_linksobjectNoThe child's tracking links, shown once on the tenant's confirming response: the sender's link confirms the handover
children[].tracking_links.sender_urlstringYes
children[].tracking_links.recipient_urlstringYes
children[].tracking_links.expires_atstring, date-timeYes
cancellationsarray of objectNoAfter dispatch, each child's answer to the cancellation; present only on that answer.
cancellations[].delivery_referencestringYesPublic reference of a delivery job or Bulk child. Pattern ^DX-[0-9A-HJKMNP-TV-Z]{8}$.
cancellations[].cancelledbooleanYes
cancellations[].reasonstring or nullYesWhy the stage policy left it, e.g. after_pickup
created_atstring, date-timeYes

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.