Skip to content
Transfer Orders

Transfer Orders

A Transfer Order moves inventory between two of your own warehouses. In the ShipHero UI, creating a transfer order from the Orders page (with order type Transfer) automatically produces two linked records:

  • An Order at the Transfer From warehouse (the one shipping the inventory out), with order number prefixed TO-.
  • A Purchase Order at the Transfer To warehouse (the one receiving the inventory), reusing the same TO-… string as its po_number.

The Public API does not expose a single transfer_order_create mutation today — to mimic the UI behavior end‑to‑end you orchestrate the two existing mutations yourself, applying the conventions ShipHero uses internally. This guide walks through that.

  1. What’s strictly required vs. UI convention
  2. Prerequisite: the “Warehouse Transfer” vendor
  3. Step 1 — Create the Order (the TO-…)
  4. Step 2 — Create the Purchase Order
  5. How Multi‑Warehouse Allocation picks up the transfer

Important

The two mutations are independent — there is no atomic guarantee. If step 2 fails after step 1 succeeded, you will be left with an orphan Order and must clean up or retry on your side. Generating the shared TO-… identifier on your side and checking for the existing Order before retrying lets you safely re-run purchase_order_create without creating duplicates.

The two records are also independent after creation: cancelling, closing, or otherwise updating one does not propagate to the other. If you change the state of either side, plan to mirror it on its counterpart.

What’s strictly required vs. UI convention

Only a few pieces are actually enforced by the backend; the rest are values the ShipHero UI uses for consistency and recognizability. Knowing the difference helps you decide where you have flexibility.

Strictly enforced by the system:

  • A vendor_id on the PO — purchase_order_create requires a vendor (any vendor works).
  • For the Order to be excluded from your dashboard’s order and revenue metrics in the ShipHero app (so internal warehouse transfers don’t inflate your sales numbers): the Order must carry at least one tag whose value starts with transfer_from_. The dashboard exclusion uses a prefix match here, so the suffix can be anything. Note that this is the only place a prefix match is applied — Multi‑Warehouse Allocation rules (see step 4) match tags as exact strings, so the same tag drives both behaviors only when it follows the full transfer_from_<id> convention.

UI conventions (recommended for compatibility, not enforced):

  • Tagging with the exact pattern transfer_from_<origin_legacy_id> and transfer_to_<destination_legacy_id>. Using the same convention the UI emits means the tags are interpretable by anyone reading them, automatically satisfy the stats‑exclusion filter, and work alongside any Multi‑Warehouse Allocation rules already configured around UI‑created transfers.
  • The TO- prefix on the order number — purely cosmetic; lets users spot transfers at a glance in the dashboard. You can use any order_number you want.
  • Reusing the same string for order_number and po_number — this is how the UI links the two records. Without it, your Order and PO will live independently and have to be correlated some other way (for example by your own external reference).
  • Using a vendor named Warehouse Transfer on the PO — keeps your transfer POs grouped consistently with UI‑created ones. Any vendor will work technically.
  • Setting the PO’s shipping_carrier and shipping_method to "Transfer" — this is just the placeholder the UI writes (these fields are normally used to track who is bringing inventory from the supplier; for a warehouse‑to‑warehouse transfer there’s no real carrier). Any string works; an empty string also works.
  • shop_name: "Manual Order" on the Order — matches what the UI submits and keeps the Order grouped with other manually‑created ones in dashboard filters.

Other touches the UI applies (placeholder recipient name Transfer Order, the destination warehouse’s address as the shipping address, zero prices) are also conventions — adapt them as makes sense for your integration.

Note

If you choose to invent your own tag scheme (for example my_transfer_from_<id>) instead of the UI’s transfer_from_<id> pattern, you’ll need to be aware of two consequences. First, your Orders won’t be picked up by the stats‑exclusion filter unless your tag still starts with transfer_from_. Second, if you also create transfer orders through the UI, you’ll need to maintain two parallel Multi‑Warehouse Allocation rules per origin warehouse — one matching transfer_from_<id> (UI) and one matching your custom tag (API) — otherwise allocation will only work for whichever flow your rule covers. Sticking to the UI’s transfer_from_<id> convention avoids both problems.

Prerequisite: the “Warehouse Transfer” vendor

The PO produced by the UI is associated with a vendor named Warehouse Transfer. To keep your transfer POs grouped consistently with the UI‑created ones, you should reference that same vendor.

First, check whether it already exists in your account using the vendors query and look for a vendor whose name is exactly Warehouse Transfer. The value you need from the response is the vendor id (a base64 GraphQL ID such as VmVuZG9yOjE1NjE2Mw==).

If it does not exist, create it once using vendor_create:

mutation {
  vendor_create(
    data: {
      name: "Warehouse Transfer"
    }
  ) {
    request_id
    complexity
    vendor {
      id
      legacy_id
      name
    }
  }
}

Reuse the returned vendor.id for every transfer PO you create afterwards.

Step 1 — Create the Order (the TO-…)

Create the Order at the Transfer From warehouse (the warehouse the inventory will ship out of). The shipping address should be the Transfer To warehouse’s address, so picking and shipping resolve to the right place.

Following the UI convention, the Order should have:

  • An order_number starting with TO-, using the same string you’ll use for po_number in step 2 (this is how the two records get linked).
  • A tags list including:
    • transfer_from_<origin_legacy_id> — the only tag the system actually consumes (it’s what excludes the Order from your dashboard’s order/revenue metrics and what Multi‑Warehouse Allocation rules match against).
    • transfer_to_<destination_legacy_id> — recommended for compatibility with UI‑created transfers and any reports or rules built around the pair.

Use the warehouse legacy_id (the numeric id) in the tag value, not the base64 GraphQL ID.

mutation {
  order_create(
    data: {
      order_number: "TO-WH1-0001"
      shop_name: "Manual Order"
      fulfillment_status: "pending"
      order_date: "2026-05-13"
      total_tax: "0.00"
      subtotal: "0.00"
      total_discounts: "0.00"
      total_price: "0.00"
      tags: ["transfer_from_8075", "transfer_to_8076"]
      shipping_lines: {
        title: "Manual Order Shipping Method"
        price: "0.00"
        carrier: ""
        method: ""
      }
      shipping_address: {
        first_name: "Transfer"
        last_name: "Order"
        company: "Destination Warehouse Name"
        address1: "742 Evergreen Terrace"
        city: "Springfield"
        state: "Oregon"
        state_code: "OR"
        zip: "97477"
        country: "United States"
        country_code: "US"
        phone: "5555555555"
      }
      line_items: [
        {
          sku: "testSKU12345"
          partner_line_item_id: "transfer-li-1"
          quantity: 10
          price: "0.00"
          product_name: "Product being transferred"
          fulfillment_status: "pending"
          quantity_pending_fulfillment: 10
          warehouse_id: "V2FyZWhvdXNlOjgwNzU="
        }
      ]
    }
  ) {
    request_id
    complexity
    order {
      id
      legacy_id
      order_number
      tags
    }
  }
}

Note

The line item warehouse_id should be the Transfer From warehouse, the same as the order itself. The Order’s shipping_lines don’t carry any special transfer semantics: the UI submits an empty carrier and method (the picker is hidden for transfer orders) and a generic title of "Manual Order Shipping Method". The "Transfer" placeholder values only appear on the PO side (step 2).

Step 2 — Create the Purchase Order

Create the PO at the Transfer To warehouse with the same po_number string you used for order_number in step 1. The vendor_id is the Warehouse Transfer vendor you fetched or created earlier.

mutation {
  purchase_order_create(
    data: {
      po_number: "TO-WH1-0001"
      po_date: "2026-05-13"
      warehouse_id: "V2FyZWhvdXNlOjgwNzY="
      vendor_id: "VmVuZG9yOjE1NjE2Mw=="
      subtotal: "0.00"
      shipping_price: "0.00"
      total_price: "0.00"
      tax: "0.00"
      discount: "0.00"
      fulfillment_status: "pending"
      shipping_carrier: "Transfer"
      shipping_method: "Transfer"
      line_items: [
        {
          sku: "testSKU12345"
          quantity: 10
          price: "0.00"
          quantity_received: 0
          quantity_rejected: 0
          product_name: "Product being transferred"
          fulfillment_status: "pending"
          sell_ahead: 0
        }
      ]
    }
  ) {
    request_id
    complexity
    purchase_order {
      id
      po_number
      warehouse_id
      vendor_id
    }
  }
}

Note

The warehouse_id on the PO is the Transfer To warehouse — the opposite of the Order. Together with the matching po_number / order_number, this is what links the two records as a single transfer.

Note

The po_number you provide is stored exactly as sent — no prefix is added or substituted. Likewise, order_number in step 1 is honored as-is. This is what allows you to use the same TO-… string for both records and link them together.

How Multi‑Warehouse Allocation picks up the transfer

The transfer_from_<origin_legacy_id> tag added in step 1 is what makes Multi‑Warehouse Allocation route the Order to the correct Transfer From warehouse for fulfillment. For this to work, your account must have a Multi‑Warehouse Allocation rule configured to match that exact tag string.

When creating the rule:

  • Set the condition to Order has tags.
  • Use the exact tag string, including the trailing numeric id, for example transfer_from_8075. Tags are matched as exact strings, so a typo, missing digit, or extra space will silently skip the rule.
  • Configure the rule’s action to prioritize (or restrict allocation to) the warehouse you want the Order fulfilled from — typically the same warehouse whose legacy_id is in the tag.

You will need one rule per Transfer From warehouse you intend to transfer from.

For details on configuring Multi‑Warehouse Allocation rules in the dashboard, see How to Use Multi‑Warehouse Allocation Rules in the ShipHero Help Center.

Note

Without a matching allocation rule the tag is informational only — the system still recognizes the Order as a transfer (and excludes it from your dashboard’s order and revenue metrics), but allocation will fall back to your default warehouse selection.