Skip to main content
Pick and Pack enables item-level fulfillment by coordinating products, store-level inventory, and delivery providers.

Quick Start

1

Set up your Product Catalog

Define your master product data with names, images, categories, and identifiers
2

Configure Store Inventory

Track per-store availability, pricing, and aisle locations for efficient picking
3

Process Pick and Pack Orders

Handle substitutions, track picking status, and manage item-level fulfillment

Product Catalog

Your product catalog serves as the master data source for all items across your stores. It defines the core product information that drives inventory management, order processing, and picking operations. Product Catalog Products are managed in bulk and referenced throughout your fulfillment workflow by inventory and orders.

Key fields

  • Required fields: name, imageUrls[], categories[], weight, dimensions{depth,height,width}, identifiers[{type,value}].
  • Optional fields: externalIdentifier, sku, description, attributes[] (e.g., WEIGHTED), and details (sizeSpecification, packSizeSpecification, weightedItemInfo).

Rate Limits

  • 10 requests per second per organization
  • 1,000 products per request

API Endpoints

Product Catalog Examples

Ready-to-use sample payloads for common product types

Store Inventory

Store inventory tracks the real-time availability, pricing, and location of products within each of your store locations. This data powers accurate order fulfillment by ensuring pickers know exactly what’s available and where to find it. Store Inventory Each inventory entry links a product to a specific store location using your external identifiers, enabling precise per-store inventory management.

Key fields

  • Required field: available (boolean).
  • Common fields: externalProductId, externalStoreLocationId, quantity, valueCents, currency, location{aisle,bay,shelf}.

Weighted Items

For variable-weight products (produce, deli items, etc.):
  1. Set the product attribute to WEIGHTED in your product catalog
  2. Use details.weightedItemInfo.valueCentsPerMeasurementUnit for per-unit pricing (e.g., price per pound)

Rate Limits

  • 10 requests per second per organization
  • 10,000 inventory items per request

API Endpoints

  • Create/Update Inventory: POST /v1/inventory - Bulk inventory updates
  • Get Inventory: GET /v1/inventory - Query inventory with filters:
    • externalStoreLocationId: Filter by store location
    • externalProductId: Filter by product
    • Pagination: pageIndex, numResultsPerPage

Pick and Pack Orders

Orders with Pick and Pack enabled leverage your product catalog and store inventory to enable precise item-level fulfillment. The system tracks exactly what was requested, what was picked, and any substitutions made during the process.
To enable Pick and Pack functionality, include pick_and_pack in the order.requirements array when creating orders. See the complete list in Order Requirements.

Order Lifecycle

  1. Order Creation: Customer places order with specific items with substitution preferences
  2. Picking: 3rd party courier picks items, handling substitutions as needed
  3. Status Updates: Real-time updates on picking progress
  4. Delivery: Order handed off to delivery provider once picking is complete

Sub-item fields

  • item.subItems[].sku: optional SKU for the specific sub-item being picked.
  • item.subItems[].substitution: object describing substitution preferences and chosen replacements.
    • preference: e.g., refund or substitute.
    • source: who made the decision (e.g., merchant, customer).
    • substituteItems[]: list of { sku, quantity, weight } representing the chosen replacements.

Status

  • items_pick_complete: emitted when item picking for the order has finished and it is ready for handoff to delivery. Track this status via your order retrieval endpoints and/or subscribe to status change events in Webhooks.

Picked items

Each delivery carries a picked_items array describing what the picker actually put in the bag. The REST response always emits the same fields per item; values are nullable, but fields are never omitted.
FieldTypeNotes
idstring | nullOrder line ID this pick fulfills. null when the SKU wasn’t a pre-existing line (substitution).
skustringSKU of what was actually picked.
namestringDisplay name of the picked item.
statusstringFulfillment status (e.g. no_substitution, pre_selected, override). Opaque pass-through from the upstream provider.
quantityintUnits picked.
requested_quantityint | nullUnits originally requested.
weightnumber | nullNet weight in kilograms for random-weight goods.
price_centsintLine price in cents (already reflects weight × unit price for weighted items).
requested_idstringID of the original order line.
requested_skustringOriginally requested SKU. Equals sku on straight picks.
scanned_barcodestring | nullRaw scan at pick time. null for loose produce; 8/12/13 digits for EAN/UPC; ≥24 for GS1 in-store labels (GTIN + weight + price + use-by).
scansarray | nullPer-scan detail for multi-scan items (e.g. a variable-weight item scanned multiple times). Each entry has its own parsed barcodes[] payload.
A substitution is identifiable by sku != requested_sku (or equivalently, id == null).

Example — straight pick, packaged item

{
  "id": "11111111-1111-1111-1111-111111111111",
  "sku": "100001",
  "name": "Dried Fruit Snack 130g",
  "status": "no_substitution",
  "quantity": 2,
  "requested_quantity": 2,
  "weight": null,
  "price_cents": 880,
  "requested_id": "11111111-1111-1111-1111-111111111111",
  "requested_sku": "100001",
  "scanned_barcode": "1234567890128",
  "scans": null
}

Example — straight pick, weighted item (single scan)

weight is populated; scanned_barcode is a GS1 in-store label encoding GTIN + weight + price. scans is null because the item was captured in a single scan.
{
  "id": "22222222-2222-2222-2222-222222222222",
  "sku": "200002",
  "name": "Free Range Chicken Tenderloins 450g-650g",
  "status": "no_substitution",
  "quantity": 1,
  "requested_quantity": 1,
  "weight": 0.608,
  "price_cents": 1307,
  "requested_id": "22222222-2222-2222-2222-222222222222",
  "requested_sku": "200002",
  "scanned_barcode": "0112345678901234152601013103000608392200130700",
  "scans": null
}

Example — substitution

{
  "id": null,
  "sku": "300099",
  "name": "Immune Support Shot 60ml",
  "status": "pre_selected",
  "quantity": 1,
  "requested_quantity": 1,
  "weight": null,
  "price_cents": 330,
  "requested_id": "33333333-3333-3333-3333-333333333333",
  "requested_sku": "300003",
  "scanned_barcode": "2345678901235",
  "scans": null
}

Example — loose produce (no scan)

Items sold “each” have no physical barcode. Identify by sku.
{
  "id": "66666666-6666-6666-6666-666666666666",
  "sku": "600006",
  "name": "Fresh Bananas Each",
  "status": "no_substitution",
  "quantity": 2,
  "requested_quantity": 2,
  "weight": null,
  "price_cents": 186,
  "requested_id": "66666666-6666-6666-6666-666666666666",
  "requested_sku": "600006",
  "scanned_barcode": null,
  "scans": null
}

Example — weighted item with multiple scans

Variable-weight items picked across multiple physical units produce one scans[] entry per scan, each with a parsed GS1 payload. For richer parsed output, read scans[].barcodes[] instead of re-parsing scanned_barcode yourself.
{
  "id": "77777777-7777-7777-7777-777777777777",
  "sku": "700007",
  "name": "Bananas Loose",
  "status": "no_substitution",
  "quantity": 1,
  "requested_quantity": 1,
  "weight": 1.45,
  "price_cents": 450,
  "requested_id": "77777777-7777-7777-7777-777777777777",
  "requested_sku": "700007",
  "scanned_barcode": "0112345678901234310300072039220000225",
  "scans": [
    {
      "substitution_type": "no_substitution",
      "barcodes": [
        {
          "barcode": "0112345678901234310300072039220000225",
          "format": "gs1_databar",
          "is_variable_weight": true,
          "weight": 0.72,
          "weight_unit": "kg",
          "price_cents": 225,
          "product_code": "12345678901234",
          "expiration_date": null
        }
      ]
    },
    {
      "substitution_type": "no_substitution",
      "barcodes": [
        {
          "barcode": "0112345678901234310300073039220000225",
          "format": "gs1_databar",
          "is_variable_weight": true,
          "weight": 0.73,
          "weight_unit": "kg",
          "price_cents": 225,
          "product_code": "12345678901234",
          "expiration_date": null
        }
      ]
    }
  ]
}