Delivery options

Updated September 9, 20268 min read

A Delivery Option is a named, merchant-configured choice presented to the customer at checkout. Each option declares its own pricing, estimated delivery time, the customer geographies it supports, the fulfillment locations it can ship from, and the delivery method (home delivery or customer collection).

The merchant builds this list once in the Dashboard. At checkout, the merchant calls Carriyo with the basket details and customer context. Carriyo returns the subset of options eligible for that specific checkout.

To compare raw carrier rates across carrier accounts (an internal, operator-facing task), see Shipping rates.

What a delivery option carries

A delivery option is configured per merchant and includes:

  • Name and description. What the customer sees.
  • Delivery method. DELIVERY (home delivery) or COLLECTION (customer collection).
  • Carrier account. The underlying account that fulfills the option once it's selected.
  • Shipping fee. Flat, tiered, or free. The merchant controls the pricing model independently of the underlying carrier rate.
  • Estimated delivery time. Used to compute the date shown to the customer at checkout.
  • Fulfillment locations. The locations the option can ship from. For collection options, the customer collection locations the option can be picked up from.
  • Eligibility criteria. Used to filter which options are returned for a given checkout. Examples include customer geography, order value bands, weight thresholds, and payment type.

The exact criteria available are managed in the Dashboard and evolve over time.

How the shipping fee is resolved

Each option prices itself, and the pricing strategy it was configured with decides how.

  • FREE_OF_COST. Always 0. The currency is the option's default currency when one is set, and absent when it isn't. A free option often arrives carrying no currency at all.
  • PAID. Carriyo takes the pricing entry matching the request currency, then the option's default currency, then the first entry configured. A tiered fee returns the first tier whose range contains the order total.
  • PASS_THROUGH. A live rate quoted by the carrier. An option saved as PASS_THROUGH without a carrier account is downgraded to FREE_OF_COST at save time. It never reaches checkout as a quote nobody can honor.

When a tiered fee ends in a free tier with no upper bound, that tier's min_value comes back as free_shipping_threshold. It's read off the pricing configuration rather than the cart, so it's there whatever the basket is currently worth. That's what powers an "add more for free shipping" nudge.

An empty shipping_fee ({}) means no price could be resolved: no tier contained the order total, or no pricing entry matched the currency. Never render it as free shipping. Read amount and currency independently: 0 is a real price, and absent is not.

That is different from a fee lookup that fails outright, which drops the option from the response rather than returning it unpriced. A failure after the fee has resolved goes the other way. The option is still returned, with its fee, but with no arrival estimate.

Two delivery methods, one model

Each delivery option declares whether it's DELIVERY (home delivery to the customer's address) or COLLECTION (customer picks up at a configured location). A single checkout call can return both kinds in the same response, and the customer picks either one. See Click and collect for the specifics of the COLLECTION variant.

How collection points are chosen

A COLLECTION option resolves its own list of pickup points at request time. Which points you get depends on how the option was configured.

An explicit list. The option offers exactly those locations, as long as each is active and enabled for collection. The list is honored as configured and is not filtered by the customer's country, so a merchant can deliberately offer a cross-border pickup point.

An empty or omitted list. The option resolves to every active, collection-enabled location the merchant can use in the customer's country. With no customer country in the request, it resolves to nothing.

fulfillment_locations reads the opposite way. An empty list there means any fulfillment location, whatever country it sits in.

Send customer.coords and Carriyo returns a distance per point and orders the list nearest-first. Two limits can narrow it: max_collection_distance on the request and customer_collection_max_distance on the option. When both are set the smaller one wins, and neither is enforced without the customer's coordinates. The list is then capped at max_collection_locations, which defaults to 10 and is never allowed above 50.

What the merchant sends at checkout

A typical request carries:

  • Merchant. Which merchant's configured options to evaluate.
  • Line items. The basket.
  • Customer address. Used to evaluate geographic eligibility.
  • Fulfillment locations. The locations the merchant has inventory for this basket. Carriyo intersects these with each option's configured fulfillment locations to decide which options can be honored.
  • Delivery methods (optional). Restrict the response to DELIVERY only, COLLECTION only, or both.

The response contains the eligible options, each with the computed shipping fee and estimated delivery date for this specific checkout.

Inventory-aware delivery options

By default, delivery options are filtered on the merchant's configured eligibility: geography, value bands, and the option's fulfillment locations. When inventory management is enabled, Carriyo adds a real-time stock check on top. An option is offered only if the basket can actually be fulfilled from the locations that option ships from, so the customer never picks a delivery choice that can't be honored. Each returned entry reports what it can provide of every basket line, and where that stock sits.

In the Dashboard: turn on inventory management and keep stock positions current. See Inventory.

Via the API: set inventory_check to true and identify the products on each line, by product_id or product_ref. Three things have to hold before the check runs. The flag is true, inventory management is enabled for the tenant, and the line resolves to a catalog product. Miss any one of them and no stock is read. Every option that passes the standard eligibility checks comes back unmarked. The flag is opt-in by design, so existing integrations keep working untouched.

One trap sits under all of this. Stock is read by the product's inventory_key, so a catalog product without one has no stock to find. The line still counts as evaluated, so it reads as out of stock rather than as unevaluated. Options then get filtered out on a check that never really ran. See Products for how the three identifiers resolve.

This is what makes the order-allocation bridge below precise. The locations reported on the chosen option are the ones that genuinely held stock. When allocation later constrains booking to them, it constrains to a set that can actually fulfill, not just a configured list.

The exact request and response fields, the activation conditions, and the fallback contract are in the Delivery options API reference. For a full walkthrough that sends product references and uses the stock-filtered response, see the Filter delivery options by stock recipe. For reading availability marks, asking for variations, and requoting a split basket, see Handle partial availability at checkout.

What one entry means

Every returned entry is a single-delivery answer. Everything the entry marks as available ships together, in one delivery, from any one of the fulfillment locations it lists, at that entry's fee. The response never quietly promises a split.

Set include_variations and Carriyo returns each maximal single-location variation as its own entry. A storefront that wants to offer several deliveries composes them from those entries, then requotes each subset so every delivery carries its own real fee.

Reading availability

Three answers, and the difference between them matters:

  • Absent. The line was never evaluated. This is not a "no".
  • UNKNOWN. The check ran and couldn't answer, typically because an inventory read failed.
  • FULL, PARTIAL, NONE. A real verdict on what the entry can provide.

An option that can't provide anything is left out of the response. A failed inventory read is the exception: the option comes back marked UNKNOWN instead of disappearing, so you decide whether to show it.

Scoping an option to some items only

An option's item_product_categories condition scopes which items it can deliver. It doesn't hide the option. Items outside the condition come back as NONE with fulfillable_quantity: 0 in every entry, even when no stock was read for them. They still count toward the entry's verdict. An option that excludes every item in the cart isn't returned at all.

A chilled-goods option is the everyday case. The option is offered, the chilled lines are available on it, and the rest of the basket is marked NONE.

The bridge to order allocation

When the customer picks a delivery option, send the chosen option's code on the order. Use the code rather than the id: the code is yours to keep stable, while the id changes if the option is deleted and recreated. When the order is sent to Carriyo:

  • The chosen option is recorded on each fulfillment order the order produces, not on the order itself. Read it from fulfillment_orders[].delivery_option.
  • If the merchant is using Carriyo's Order management with allocation, allocation restricts to the fulfillment locations attached to the option. The customer can't end up with a shipment booked from a location the option doesn't support.

The option's estimated delivery date is only achievable when the shipment ships from a location the option was configured for. Constraining allocation to those locations keeps the customer-facing promise intact.

The estimated delivery date becomes a promise

The estimated delivery date returned per option is what the customer sees at checkout as their expected delivery. When the order is placed, the date for the chosen option becomes the customer promise on the resulting shipment. Tracking against that promise is handled by Service levels once the shipment exists.

How it fits with other modules