Filter delivery options by stock
When inventory management is enabled for the tenant, POST /checkout/delivery-options
can filter its result by real-time stock. Send a product_id on each line item and
Carriyo matches the basket against inventory at each option's eligible locations. Every
returned option then carries two extra fields: items (the lines it can fulfill, with
quantity) and fulfillment_locations (where that stock sits). An option that can't
fulfill a single line is dropped from the response entirely. This recipe covers only the
inventory-aware behavior; for the base endpoint walkthrough without inventory, see
Show delivery options at checkout.
Scenario
A customer in London is checking out two items: a camera and a laptop. Your storefront
calls Carriyo with both line items carrying a product_id. Carriyo checks real-time stock
at each delivery option's eligible locations and returns only the options that can ship at
least one of the items, each annotated with what it can fulfill and from which locations.
You can either let Carriyo check all configured locations automatically, or pass specific
candidate locations to narrow the scope. Either way, you persist the chosen option together
with its fulfillment_locations on the order, so allocation is constrained to a
fulfillable set.
Prerequisites
- API credentials: see Getting started for the one-time setup.
- Inventory management enabled for the tenant. Without it, the stock check is skipped and the endpoint behaves as in the base recipe. See Fallback below.
- At least one delivery option configured for the merchant, with associated fulfillment locations.
- Stock recorded against those
product_ids at one or more of those locations.
Step 1, send the checkout request with a product reference per line item
Set product_id on every line item. That's the only trigger — inventory filtering
activates when all three conditions hold: inventory management is enabled for the tenant,
the request includes line_items, and at least one line item has a product_id. Miss any
one and the stock check is skipped.
You have two options for how locations are resolved:
Option A — let Carriyo find eligible locations
Omit fulfillment_locations from the request entirely. Carriyo checks stock at all
locations configured on each delivery option. This is the simpler integration: your
storefront doesn't need to know anything about warehouse topology — just send the cart
with product references and Carriyo handles the rest.
curl -X POST 'https://api.carriyo.com/checkout/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",
"delivery_methods": ["DELIVERY"],
"payment": {
"currency": "GBP",
"order_total": 763.74
},
"line_items": [
{
"id": "line-1",
"sku": "SKU-CAM-001",
"product_id": "PROD-CAM-001",
"quantity": 1,
"weight": { "value": 0.5, "unit": "kg" }
},
{
"id": "line-2",
"sku": "SKU-LAP-002",
"product_id": "PROD-LAP-002",
"quantity": 1,
"weight": { "value": 1.5, "unit": "kg" }
}
],
"customer": {
"country": "GB",
"city": "London",
"address1": "45 Gloucester Place",
"postcode": "W1U 8HU",
"contact_name": "Oliver Bennett"
}
}'
In this case, if Standard Delivery is configured with locations WH-UK-01,
WH-UK-02, and STORE-UK-LON-01, Carriyo checks stock at all three and returns the ones
that hold inventory for the basket.
Option B — scope to specific locations
Pass fulfillment_locations when your storefront already knows which warehouses or stores
should be considered (e.g. from a prior availability check, or because the merchant only
ships from certain locations for this customer). Carriyo intersects the supplied locations
with each option's configured allowlist, then checks stock only at that intersection.
curl -X POST 'https://api.carriyo.com/checkout/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",
"delivery_methods": ["DELIVERY"],
"payment": {
"currency": "GBP",
"order_total": 763.74
},
"line_items": [
{
"id": "line-1",
"sku": "SKU-CAM-001",
"product_id": "PROD-CAM-001",
"quantity": 1,
"weight": { "value": 0.5, "unit": "kg" }
},
{
"id": "line-2",
"sku": "SKU-LAP-002",
"product_id": "PROD-LAP-002",
"quantity": 1,
"weight": { "value": 1.5, "unit": "kg" }
}
],
"customer": {
"country": "GB",
"city": "London",
"address1": "45 Gloucester Place",
"postcode": "W1U 8HU",
"contact_name": "Oliver Bennett"
},
"fulfillment_locations": [
{ "partner_location_code": "WH-UK-01" },
{ "partner_location_code": "STORE-UK-LON-01" }
]
}'
Here you're telling Carriyo: "only consider these two locations." If Standard Delivery
is configured with WH-UK-01, WH-UK-02, and STORE-UK-LON-01, only WH-UK-01 and
STORE-UK-LON-01 are checked for stock (the intersection). An option whose configured
locations have no overlap with your supplied list is excluded entirely.
Which to choose
| Approach | When to use |
|---|---|
| Option A (omit locations) | Your storefront doesn't track inventory sourcing. Let Carriyo's configured locations drive the check. Simpler integration. |
| Option B (pass locations) | You've already determined which locations can source the order (e.g. from your OMS or a prior inventory lookup). Narrows the scope for a more precise result. |
The response shape is identical in both cases.
Step 2, read the stock-filtered response
Each returned option now carries items and fulfillment_locations:
[
{
"id": "012694a1-04af-42fd-ba4d-beeaca2034ed",
"delivery_method": "DELIVERY",
"code": "STANDARD_DELIVERY",
"name": "Standard Delivery",
"carrier_account_id": "782b9b83-6d95-4468-8998-38f28b5a7c11",
"description": "Delivered in 2-4 working days.",
"shipping_fee": { "amount": 4.99, "currency": "GBP" },
"estimated_arrival_from": "2026-05-09T09:00:00.000Z",
"estimated_arrival_to": "2026-05-11T18:00:00.000Z",
"items": [
{ "line_item_id": "line-1", "quantity": 1 },
{ "line_item_id": "line-2", "quantity": 1 }
],
"fulfillment_locations": ["d4f5a6b7-1111-4c8d-9e0f-1a2b3c4d5e6f"]
},
{
"id": "f29acd11-34c2-4874-96f6-4a2530a82886",
"delivery_method": "DELIVERY",
"code": "EXPRESS_DELIVERY",
"name": "Express Delivery",
"carrier_account_id": "1ee1b219-d64e-4e67-8bc4-ff1c8c955457",
"description": "Next-day delivery across the UK.",
"shipping_fee": { "amount": 9.99, "currency": "GBP" },
"estimated_arrival_from": "2026-05-08T09:00:00.000Z",
"estimated_arrival_to": "2026-05-08T18:00:00.000Z",
"items": [
{ "line_item_id": "line-1", "quantity": 1 }
],
"fulfillment_locations": ["f6a7c8d9-3333-4e0f-1a2b-3c4d5e6f7a8b"]
}
]
items[].line_item_idechoes the request line'sid;items[].quantityis the amount that option can fulfill.fulfillment_locationslists the partner location IDs of the option's configured locations that hold stock for those items (not location codes). When at least one location can fulfill every listed item, only those shared locations are returned; otherwise the field falls back to the union of locations that fulfill at least one item.
The partial-stock case
Express Delivery above is a partial-stock option. Its only eligible location
(the London store, f6a7c8d9-3333-4e0f-1a2b-3c4d5e6f7a8b) stocks the camera (line-1) but not the laptop (line-2), so the
laptop is excluded and items lists line-1 only. The option is still returned, and it can
ship part of the basket. Render it accordingly (e.g. "1 of 2 items ships express"), or
split the basket across options at checkout.
The no-stock case
An option whose eligible locations stock none of the requested lines is dropped from
the response entirely. It never comes back with an empty items array. In this scenario,
if a third option (say SAME_DAY) had no stock for either line at any of its locations,
it simply wouldn't appear in the list above. Only Standard (both items) and Express
(camera only) survive the filter.
Fallback, omit product_id and the stock check doesn't run
Drop product_id from the line items and inventory filtering is skipped, so the endpoint
behaves exactly as it did before this feature. The same is true when inventory management
is disabled for the tenant. In that mode:
itemsechoes back all request line items that have a non-blankid(no stock filtering applied).fulfillment_locationsreturns each option's configured fulfillment location IDs, or is omitted when the option has no configured locations.- No options are dropped on stock grounds.
curl -X POST 'https://api.carriyo.com/checkout/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",
"delivery_methods": ["DELIVERY"],
"payment": { "currency": "GBP", "order_total": 763.74 },
"line_items": [
{ "id": "line-1", "sku": "SKU-CAM-001", "quantity": 1, "weight": { "value": 0.5, "unit": "kg" } },
{ "id": "line-2", "sku": "SKU-LAP-002", "quantity": 1, "weight": { "value": 1.5, "unit": "kg" } }
],
"customer": {
"country": "GB",
"city": "London",
"address1": "45 Gloucester Place",
"postcode": "W1U 8HU",
"contact_name": "Oliver Bennett"
}
}'
Existing integrations that don't send product_id keep working unchanged. The feature is
fully back-compatible.
Step 3, persist the chosen option and its fulfillment locations
The customer picks Standard Delivery, the only option that ships the whole basket.
Persist the option's code on the order so allocation knows which option was sold, and
constrain allocation to that option's fulfillment_locations so it can only pick a
location that actually holds the stock.
{
"merchant": "ACME",
"partner_order_reference": "YOUR_ORDER_REF",
"delivery_option": { "code": "STANDARD_DELIVERY" },
"fulfillment_locations": [
{ "partner_location_id": "d4f5a6b7-1111-4c8d-9e0f-1a2b3c4d5e6f" }
]
// ...rest of the order create payload
}
For the full order create call and its allocated response, including how Carriyo splits the order into fulfillment orders, follow Place an order with a delivery option. That recipe picks up exactly where this one leaves off.
What just happened
- You sent the cart with a
product_idper line, so Carriyo checked real-time stock at each option's eligible locations instead of returning every configured option blind. - Each returned option reported the lines it can fulfill (
items) and where the stock is (fulfillment_locations). Standard can ship both items from the central warehouse; Express can ship only the camera, from the London store. - Any option that couldn't fulfill a single line was dropped before you ever saw it.
- Persisting the chosen option plus its
fulfillment_locationson the order keeps allocation inside a set you already know can fulfill the basket.
Pitfalls
product_idis the trigger. Omit it on every line and the stock check never runs, so you get the unfiltered, pre-feature behavior. If you expect filtering and don't seeitemsreflecting stock, check theproduct_ids are present and inventory management is enabled.- Partial-stock options still return. An option that can fulfill some lines comes
back with a trimmed
itemslist, not an error. Decide in your UI whether to show it, hide it, or split the basket. - No-stock options vanish silently. They're omitted, never returned with empty
items. Don't write code that expects every configured option to appear. fulfillment_locationsare partner location IDs, not codes. The response returns IDs; pass them back on the order create aspartner_location_id. Shared locations when one location covers every item; the union otherwise. Persist whatever the chosen option returned rather than recomputing it yourself.- Currency must match across the delivery-options request, the option, and the order create payload.