Filter delivery options by stock

Updated June 11, 20266 min read

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

ApproachWhen 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_id echoes the request line's id; items[].quantity is the amount that option can fulfill.
  • fulfillment_locations lists 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:

  • items echoes back all request line items that have a non-blank id (no stock filtering applied).
  • fulfillment_locations returns 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_id per 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_locations on the order keeps allocation inside a set you already know can fulfill the basket.

Pitfalls

  • product_id is 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 see items reflecting stock, check the product_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 items list, 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_locations are partner location IDs, not codes. The response returns IDs; pass them back on the order create as partner_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.