Skip to content
Wholesale Orders Packed Outside ShipHero

Wholesale Orders Packed Outside ShipHero

This complete flow leverages our Public API to enable third-party systems to fully automate B2B fulfillment, drastically reducing manual operational time while keeping all ShipHero data perfectly accurate and in real-time sync.

When handling Wholesale Orders that are packed outside ShipHero, the Public API allows you to manage the entire flow — from creation to fulfillment — programmatically, without touching the ShipHero UI.

Below you’ll find each step in order, exactly as tested.

Warning

To use the wholesale_order_fulfill mutation, the order can’t be from SPS, there must be no valid shipping labels, the wholesale order status must be Packing and the packing layout must be 100% empty.

Statuses and transitions

Before diving into the flow, it’s important to understand how wholesale order statuses work in ShipHero. Each wholesale order goes through a series of well-defined states —from creation and inventory reservation to final fulfillment— that reflect its progress through warehouse operations. Transitions between these states are governed by strict business rules: inventory must be allocated before picking starts, packing can only occur after picking completes, and Fulfilled marks the point of no return when inventory is deducted and stores are notified.

1. Create the Wholesale Order

First, create a wholesale order.
You must include a valid SKU that already exists in your account.

mutation {
  wholesale_order_create(
    data: {
      partner_order_id: "WHOLESALE-1004"
      shop_name: "My B2B Channel"
      order_number: "OR-04"
      tags: ["wholesale"]
      shipping_address: {
        first_name: "Customer"
        last_name: "Wholesale"
        address1: "123 Street"
        city: "Santa Fe"
        state: "SF"
        zip: "3000"
        country: "AR"
      }
      line_items: [
        {
          sku: "WHITE-TSHIRT-M"
          quantity: 12
          price: "15.00"
          partner_line_item_id: "LINE-WHITE-TSHIRT-M-04"
        }
      ]
      allow_partial: false
      picking_flow: DESKTOP
    }
  ) {
    request_id
    wholesale_order {
      id
      picking_flow
      order {
        id
        order_number    
        fulfillment_status
      }
    }
  }
}

In this example we’re using manual picking (picking_flow: DESKTOP).

2. Get Available Staging Locations

To see which staging bins are available, use:

query {
  wholesale_staging_location_candidates(warehouse_id: "V2FyZWhvdXNlOjE1MDU2") {
    request_id
    data {
      edges {
        node {
          id
          name
        }
      }
    }
  }
}

Example result:

{
  "data": {
    "wholesale_staging_location_candidates": {
      "data": {
        "edges": [
          { "node": { "id": "QmluOjI0MTYxMjY5", "name": "EAD_II_11" } }
        ]
      }
    }
  }
}

Avoid using a staging location that is already in use.

3. Assign a Staging Location

Assign one of the free locations from the list:

mutation {
  wholesale_order_update_staging_location(
    data: {
      order_id: "T3JkZXI6Njg2MzAwMDEz"   # new order id for OR-04
      staging_location_id: "QmluOjI0MTYxMjY5" # EAD_II_11
    }
  ) {
    request_id
    wholesale_order {
      id
      staging_location_id
      status
      order { id order_number }
    }
  }
}

4. Allocate Inventory for Picking

Now allocate inventory for this order.

mutation {
  wholesale_order_auto_allocate_for_picking(
    data: {
      order_id: "T3JkZXI6Njg2MzAwMDEz"
      prioritized_location_ids: ["V2FyZWhvdXNlOjE1MDU2"]
    }
  ) {
    request_id
    complexity
    line_items {
      line_item_id
      quantity
      product { sku }
      allocations {
        __typename
      }
    }
  }
}

A successful allocation will show "__typename": "AllocatedAllocationType".

5. Mark as Ready to Pick

After allocation:

mutation {
  wholesale_set_as_ready_to_pick(
    data: { order_id: "T3JkZXI6Njg2MzAwMDEz" }
  ) {
    request_id
    ok
  }
}

The order now moves from defaultready_to_pick.

6. Print the Manual Picking Sheet

Generate a PDF with manual picking instructions:

mutation {
  wholesale_order_print_manual_picking_sheet(
    data: { order_id: "T3JkZXI6Njg2MzAwMDEz" }
  ) {
    request_id
    picking_sheet_url
  }
}

Response example:

{
  "data": {
    "wholesale_order_print_manual_picking_sheet": {
      "picking_sheet_url": "https://d2s9s6xmfnfu1v.cloudfront.net/wholesale/picking_slips/2025-10-10/picking_slip_686300013.pdf"
    }
  }
}

This PDF can be printed and handed to warehouse staff for manual picking.

7. Transfer Picks to Staging

After picking is done, move items into staging.

mutation {
  wholesale_order_transfer_picks_to_staging(
    data: { order_id: "T3JkZXI6Njg2MzAwMDEz" }
  ) {
    request_id
    wholesale_order {
      id
      status
      staging_location_id
      order { id order_number }
    }
  }
}

Status should now read packing.

8. Inspect the Staged Manifest (Optional)

Once picks are in staging, you can query the staged inventory manifest to see exactly what is sitting in the staging location for this order, broken down per line item by SKU, lot, container type (eaches vs cases), quantity, and bin. This is especially useful when your packing is driven by an external system: it tells you which lots were allocated, what’s available to pack, and gives you the IDs you need to build the wholesale_order_import_packing_layout payload in the next step.

The manifest is exposed as a staged_inventory field on each wholesale_line_item:

query {
  wholesale_order(order_id: "T3JkZXI6Njg2MzAwMDEz") {
    request_id
    data {
      id
      status
      wholesale_line_items {
        edges {
          node {
            id
            staged_inventory {
              line_item_id
              sku
              quantity
              case_type
              lot_id
              lot_name
              lot_expiration_date
              location_id
              location_name
            }
          }
        }
      }
    }
  }
}

Example response:

{
  "data": {
    "wholesale_order": {
      "data": {
        "wholesale_line_items": {
          "edges": [
            {
              "node": {
                "id": "V2hvbGVzYWxlTGluZUl0ZW06MTIz",
                "staged_inventory": [
                  {
                    "line_item_id": "TGluZUl0ZW06OTg3Ng==",
                    "sku": "WHITE-TSHIRT-M",
                    "quantity": 12,
                    "case_type": null,
                    "lot_id": "TG90OjQzMg==",
                    "lot_name": "LOT-2026-04",
                    "lot_expiration_date": "2027-04-01T00:00:00",
                    "location_id": "QmluOjI0MTYxMjY5",
                    "location_name": "EAD_II_11"
                  }
                ]
              }
            }
          ]
        }
      }
    }
  }
}

Notes on the response:

  • Only items still pending packing are returned. Once a row is fully packed, it disappears from the manifest — so an empty list for a line item means everything staged has already been packed.
  • case_type is null for loose eaches and set (for example, CASE, INNER_CASE, MASTER_CASE) when the staged unit is a product case.
  • lot_id, lot_name, and lot_expiration_date are only populated for lot-tracked products.
  • line_item_id is the order line item GID. You can pass this value directly into the order_line_item_id field of the packing_data JSON in step 9 — no translation needed.

Tip

Treat the manifest as the source of truth when assembling the import payload: the SKUs, quantities, and line_item_id values it returns are the same ones the import mutation expects.

9. Import Packing Layout (Optional)

If your packing is managed by an external system and you want to reflect the full container structure (pallets, packages, case packs, and line items) in ShipHero before fulfilling, you can import the packing layout at this point.

The order must be in packing status (after step 7) and must not have an existing packing configuration.

mutation {
  wholesale_order_import_packing_layout(
    data: {
      order_id: "T3JkZXI6Njg2MzAwMDEz"
      packing_data: "{\"containers\": [ ... ]}"
    }
  ) {
    request_id
    complexity
    wholesale_order {
      id
      status
    }
  }
}

For the full JSON schema, container types, numbering rules, and validation details, see Import Packing Layout.

Note

This step is optional. If you skip it, you can still fulfill the order using is_packed_externally: true in the next step, which does not require a packing layout.

10. Fulfill the Order (Packed Externally)

Finally, after you externally packed and labeled packages, mark the order as fulfilled:

mutation {
  wholesale_order_fulfill(
    data: {
      order_id: "T3JkZXI6Njg2MzAwMDEz"
      is_packed_externally: true
    }
  ) {
    request_id
    wholesale_order {
      id
      status
    }
  }
}

Expected response:

{
  "data": {
    "wholesale_order_fulfill": {
      "wholesale_order": {
        "status": "fulfilled"
      }
    }
  }
}

The order is now fully fulfilled, just like an internal ShipHero order.

11. Verify the Order Status

Check the final state:

query {
  wholesale_order(order_id: "T3JkZXI6Njg2MzAwMDEz") {
    request_id
    data {
      id
      status
      picking_flow
      staging_location_id
      order {
        order_number
        fulfillment_status
      }
    }
  }
}

Summary Table

StepMutationDescription
1wholesale_order_createCreate wholesale order (picking_flow: DESKTOP)
2wholesale_staging_location_candidatesGet staging locations
3wholesale_order_update_staging_locationAssign staging bin
4wholesale_order_auto_allocate_for_pickingAllocate inventory
5wholesale_set_as_ready_to_pickMark ready to pick
6wholesale_order_print_manual_picking_sheetPrint picking PDF
7wholesale_order_transfer_picks_to_stagingMove to staging
8wholesale_order (staged_inventory)Inspect the staged manifest (optional)
9wholesale_order_import_packing_layoutImport packing layout (optional)
10wholesale_order_fulfillFulfill packed externally
11wholesale_orderVerify final status