Show delivery options at checkout
Your cart or checkout page has to show the shopper what they can pick: standard delivery,
next-day, free store pickup. POST /storefront/delivery-options returns exactly that set.
You supply the cart details and the customer's location; Carriyo evaluates each
merchant-configured option against its eligibility conditions and returns the ones that
match, with shipping fee and estimated arrival window.
Both home-delivery (DELIVERY) and click-and-collect (COLLECTION) options come back in
the same response, coverage-banded and sorted. This recipe walks through the standard call,
explains the response shape for each delivery method, covers ordering, and shows how to
scope results when your UI separates the two.
Scenario
A customer in Dubai is checking out a cart worth AED 285 containing two items. Your storefront calls Carriyo with the customer's address and the basket. Carriyo returns three options: Standard Delivery, Next Day Delivery, and a free store-pickup option with two nearby collection points sorted by distance.
Prerequisites
- API credentials: see Getting started for the one-time setup.
- At least one delivery option configured for the merchant in the Dashboard. Options must have eligibility conditions that match the scenario (e.g. customer geography covers Dubai, order value falls within the configured band).
Step 1, call the endpoint with cart and customer details
The minimum required field is merchant. In practice you'll also send payment (for
order-value conditions), line_items (for weight conditions), and customer (for
geography conditions and estimated-arrival computation).
curl -X POST 'https://api.carriyo.com/storefront/delivery-options' \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
-H 'tenant-id: YOUR_TENANT_ID' \
-H 'x-api-key: YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"merchant": "ACME",
"sort_by": "SPEED",
"payment": {
"currency": "AED",
"order_total": 285
},
"customer": {
"country": "AE",
"state": "DUBAI",
"city": "DUBAI",
"area": "DOWNTOWN-DUBAI",
"coords": [25.197, 55.274]
},
"line_items": [
{
"id": "line-1",
"sku": "TSHIRT-BLK-M",
"category": "apparel",
"quantity": 2,
"weight": { "value": 0.25, "unit": "kg" }
},
{
"id": "line-2",
"sku": "JEANS-W32-L32",
"category": "apparel",
"quantity": 1,
"weight": { "value": 0.6, "unit": "kg" }
}
]
}'
Carriyo evaluates each option's configured conditions against this data:
| Condition | Evaluated against |
|---|---|
| Customer geography | customer.country, state, city, area |
| Order value range | payment.order_total |
| Weight range | Total order weight: sum of line_items[].weight × quantity (converted to kg) |
| Payment type | CASH_ON_DELIVERY if payment.payment_on_delivery > 0, else PRE_PAID |
| Product categories | Line-item categories, taken from line_items[].category or the product catalog |
| Working days / blackout days | Current time; timezone from customer.country |
| Fulfillment location | fulfillment_locations[] (omitted here; see Scope to specific fulfillment locations) |
weight.value is per unit. Carriyo multiplies it by quantity to get the line's
contribution. Options that fail any configured condition are excluded from the response.
Step 2, read the response
The response is an array of matching options. Both DELIVERY and COLLECTION options
appear together:
[
{
"id": "a1b2c3d4-1234-5678-abcd-111111111111",
"delivery_method": "DELIVERY",
"code": "STD",
"name": "Standard Delivery",
"description": "Delivered in 2–4 working days",
"carrier_account_id": "e7f8a9b0-1234-4c5d-8e6f-7a8b9c0d1e2f",
"shipping_fee": { "amount": 15, "currency": "AED" },
"estimated_arrival_from": "2026-05-07T09:00:00.000Z",
"estimated_arrival_to": "2026-05-09T18:00:00.000Z"
},
{
"id": "a1b2c3d4-1234-5678-abcd-222222222222",
"delivery_method": "DELIVERY",
"code": "NEXT_DAY",
"name": "Next Day Delivery",
"description": "Order before 6pm for next working day",
"carrier_account_id": "d6e7f8a9-5678-4b0c-9d1e-2f3a4b5c6d7e",
"shipping_fee": { "amount": 25, "currency": "AED" },
"estimated_arrival_from": "2026-05-06T09:00:00.000Z",
"estimated_arrival_to": "2026-05-06T18:00:00.000Z"
},
{
"id": "a1b2c3d4-1234-5678-abcd-333333333333",
"delivery_method": "COLLECTION",
"code": "STORE_PICKUP",
"name": "Collect From Store",
"description": "Free pickup from a participating store",
"shipping_fee": { "amount": 0, "currency": "AED" },
"estimated_arrival_from": "2026-05-06T10:00:00.000Z",
"estimated_arrival_to": "2026-05-06T22:00:00.000Z",
"customer_collection_locations": [
{
"location_id": "loc-dxb-downtown",
"location_code": "STORE-DXB-DOWNTOWN",
"location_name": "ACME Downtown Dubai",
"address1": "Boulevard Plaza, Tower 1",
"city": "DUBAI",
"state": "DUBAI",
"country": "AE",
"coords": [25.197, 55.274],
"distance": { "value": 0.4, "unit": "km" }
},
{
"location_id": "loc-dxb-marina",
"location_code": "STORE-DXB-MARINA",
"location_name": "ACME Marina Mall",
"address1": "Marina Mall, Ground Floor",
"city": "DUBAI",
"state": "DUBAI",
"country": "AE",
"coords": [25.078, 55.144],
"distance": { "value": 12.3, "unit": "km" }
}
]
}
]
Every option carries the same core fields:
codeis the merchant-defined identifier you'll pass back when creating the order.shipping_feeis what to display to the customer.estimated_arrival_from/estimated_arrival_togive the delivery window.carrier_account_idis the carrier account that fulfills this option (a UUID). Present on options backed by a carrier account; absent on digital or unconfigured options.
How the results are ordered
The array is coverage-banded first. Options that can cover every line item come before options that cover only some; within the partial band, options are ordered by how many line items they cover. This keeps the choices a shopper can fully order at the top.
Within each band, sort_by decides the order:
SPEED(the default): earliestestimated_arrival_tofirst.COST: lowestshipping_feefirst.
Render the list in the order Carriyo returns it. Don't re-sort client-side, or you undo the coverage banding.
Step 3, understand the COLLECTION response shape
COLLECTION options include customer_collection_locations, an array of pickup points
the customer can choose from. Each location carries address fields and, when customer.coords
was supplied in the request, a distance object showing how far it is from the customer.
Points are sorted nearest-first. You can cap results with two request fields:
max_collection_locationslimits how many points come back per option (default 10, max 50).max_collection_distancereturns only points within this radius (requirescustomer.coords).
If the customer doesn't grant location access and you can't supply coords, the points
come back in the merchant's configured order with no distance field.
Scope to specific fulfillment locations
When your storefront knows which warehouses or stores can source the order (e.g. from a
prior inventory check), pass them in fulfillment_locations. Carriyo intersects them with
each option's configured location allowlist and only returns options that overlap:
curl -X POST 'https://api.carriyo.com/storefront/delivery-options' \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
-H 'tenant-id: YOUR_TENANT_ID' \
-H 'x-api-key: YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"merchant": "ACME",
"payment": { "currency": "AED", "order_total": 450 },
"fulfillment_locations": [
{ "partner_location_code": "store-ad-marina-mall" },
{ "partner_location_code": "warehouse-dxb-01" }
],
"customer": {
"country": "AE",
"state": "ABU-DHABI",
"city": "ABU-DHABI"
},
"line_items": [
{ "id": "line-1", "sku": "WATCH-SS-42", "quantity": 1, "weight": { "value": 0.4, "unit": "kg" } }
]
}'
Use partner_location_code (your own identifier) rather than partner_location_id
(Carriyo's internal ID). Codes are stable across environments and don't require storing
Carriyo-specific IDs.
Each returned option reports which locations can fulfill it under fulfillment_locations, as
{partner_location_id, partner_location_code, partner_location_name} objects:
"fulfillment_locations": [
{
"partner_location_id": "loc-ad-marina",
"partner_location_code": "store-ad-marina-mall",
"partner_location_name": "ACME Marina Mall Store"
}
]
Filter by delivery method
If your UI shows home delivery and click-and-collect in separate tabs, use
delivery_methods to fetch only the relevant set:
// Home delivery tab only
{ "merchant": "ACME", "delivery_methods": ["DELIVERY"], ... }
// Click-and-collect tab only
{ "merchant": "ACME", "delivery_methods": ["COLLECTION"], ... }
Omit the field to get both in a single call.
Handle cash on delivery
Carriyo derives the payment type from payment.payment_on_delivery: greater than 0 means
CASH_ON_DELIVERY, zero or absent means PRE_PAID. For orders paid at the doorstep, set
payment.payment_on_delivery to the amount the courier should collect. That activates
payment-type conditions, so options restricted to prepaid orders drop out:
{
"merchant": "ACME",
"payment": {
"currency": "AED",
"order_total": 199,
"payment_on_delivery": 199
},
...
}
What to do with the chosen option
Once the customer picks an option, persist its code on the order you send to Carriyo.
This lets the allocation engine know which delivery promise was sold and constrains
fulfillment to locations the option supports:
{
"merchant": "ACME",
"partner_order_reference": "YOUR_ORDER_REF",
"delivery_option": { "code": "STD" }
// ...rest of the order payload
}
For the full order-creation flow, see Place an order with a delivery option.
Pitfalls
- Empty response means no match. The array is empty (
[]) when no configured option passes the conditions for the supplied cart. Check the customer address fields and order value match at least one option's eligibility rules. customer.coordsis optional but unlocks distance. Without it,COLLECTIONoptions still return but you get no distance sorting or distance cap enforcement.- Conditions only fire when configured. If the merchant hasn't configured a weight
condition on any option, sending
line_itemswith weights has no filtering effect. The data is accepted but not evaluated. - Currency must be consistent. The
payment.currencyshould match the currency the merchant's options are configured in. - This endpoint is stateless. No shipment or order is created. Call it as many times as you need (cart changes, address updates) without side effects.