Skip to main content

How to adapt your app to manage stock from multiple locations

We are doing changes in our platform to support multi location inventory in 2023. This guide will explain how our APIs are changing and how apps can adapt to them.

Supporting multi location inventory means:

  • Tracking inventory through multiple locations.
  • Being able to choose the priority of the locations from which the stock is taken for an order.
  • Allow shipping from multiple locations.
  • Return inventory to the appropriate locations if an order is cancelled.
  • Track stock movements at each location.

During the buying process, if there is a location that can deliver the entire order then the stock will be fulfilled from that location. If a single location cannot fulfill the whole order, then we will split the order fulfillment across different locations by creating multiple fulfillment. Merchants will be able to see the shipping option chosen for every different fulfillment.

What does this mean for applications?

Will your application be affected by these changes?

Your application will probably need to make changes if it uses at least one of the following scopes:

  • read_products
  • write_products
  • read_orders
  • write_orders
  • read_draft_orders
  • write_draft_orders
  • read_shipping
  • write_shipping

API changes

We are going to introduce new objects in our APIs to be able to model multi location inventory.

drawing

New API objects (in purple) and their relationships.

Location API

With the introduction of multi location inventory, we evolved our merchants "store branches” into “locations”. These locations will now be connected to orders, shipping, and inventory.

We launched the Location API that allows apps to manage store's locations. In order to access these endpoints, new read_locations/write_locations permissions are needed.

Important: Apps will automatically receive the read_locations scope on all merchants' access_tokens if it already has any of the following scopes:

  • read_shipping
  • write_shipping
  • read_products
  • write_products

Product and Product Variant APIs

In order to support inventory at multiple locations, we are changing where we keep inventory information. The Product Variant will stop having a single stock to have a list of inventory levels (Inventory_Level) which track the stock that exists in each location.

variant: {
id: 1234,
price: 25.00,
stock: 20,
+ inventory_levels: [...] \
}
inventory_levels: [
{
location_id: "01GR92YTFMN5CCBRHED586BTRG",
+ stock: 5
},
{
location_id: "01GR92ZRKF3G5P341CCTBKR35Y",
+ stock: 15
}
]

Important: to maintain backwards compatibility, variant.stock will exist with the total stock of the variant in all its locations. This means that if the store does not manage stock in multiple locations, apps will continue to work without any issues. But, if the store manages its stock in multiple locations, apps will not be ready to manage stock changes effectively.

Order and Fulfillment Order APIs

A new Fulfillment Order has been created to represent each fulfillment (shipments) linked to an order. Since a single line item in an order can be fulfillment from multiple locations (only if quantity is more than 1) we have also created a Fulfillment Order Line Item object to model this scenario.

Managing multiple fulfillments

In order to support multiple fulfillment per order, we are changing where we store information associated with shipments. The order will stop having the shipping information and will start to have a list of fulfillments (IDs or details).

order: {
id: 5678,
- shipping: X,
- shipping_*: X,
- shipped_at: X,
+ fulfillments: [...]
}

This is described in details in the new Fulfillment Order API and the updated Order API.

In this table we show exactly how each field that was formerly in Orders is mapped to its respective value in each Fulfillment Order:

Order V1Fulfillment Orders
order.shipping_namefulfillment_order.recipient.name
order.shipping_phonefulfillment_order.recipient.phone
order.shipping_addressfulfillment_order.destination.street
order.shipping_numberfulfillment_order.destination.number
order.shipping_floorfulfillment_order.destination.floor
order.shipping_localityfulfillment_order.destination.locality
order.shipping_zipcodefulfillment_order.destination.zipcode
order.shipping_cityfulfillment_order.destination.city
order.shipping_provincefulfillment_order.destination.province.name
order.shipping_countryfulfillment_order.destination.country.name
order.shipping_min_daysfulfillment_order.shipping.min_delivery_date (non compatible type)
order.shipping_max_daysfulfillment_order.shipping.max_delivery_date (non compatible type)
order.shipping_cost_ownerfulfillment_order.shipping.owner_cost.value
order.shipping_cost_customerfulfillment_order.shipping.owner_customer.value
order.shippingfulfillment_order.shipping.carrier_id
order.shipping_optionfulfillment_order.shipping.option_name
order.shipping_option_codefulfillment_order.shipping.option_code
order.shipping_option_referencefulfillment_order.shipping.option_reference
order.shipping_pickup_details.*fulfillment_order.shipping.pickup_details
order.shipping_pickup_details.namefulfillment_order.shipping.pickup_details.name
order.shipping_pickup_details.addressfulfillment_order.shipping.pickup_details.address
order.shipping_pickup_details.cityfulfillment_order.shipping.pickup_details.city
order.shipping_pickup_details.provincefulfillment_order.shipping.pickup_details.province.name
order.shipping_pickup_details.pickup_hoursfulfillment_order.shipping.pickup_details.pickup_hours
order.shipping_tracking_numberfulfillment_order.tracking_info.number
order.shipping_tracking_urlfulfillment_order.tracking_info.url
order.shipping_store_branch_namefulfillment_order.shipping.pickup_details.name
order.shipping_pickup_typefulfillment_order.shipping.type
order.shipping_suboptionfulfillment_order.shipping.pickup_details
order.shipping_suboption.idfulfillment_order.shipping.pickup_details.location_id
order.shipping_carrier_namefulfillment_order.shipping.carrier_name
order.shipping_address.namefulfillment_order.recipient.name
order.shipping_address.phonefulfillment_order.recipient.phone
order.shipping_address.addressfulfillment_order.destination.street
order.shipping_address.numberfulfillment_order.destination.number
order.shipping_address.floorfulfillment_order.destination.floor
order.shipping_address.localityfulfillment_order.destination.locality
order.shipping_address.zipcodefulfillment_order.destination.zipcode
order.shipping_address.cityfulfillment_order.destination.city
order.shipping_address.provincefulfillment_order.destination.province.name
order.shipping_address.countryfulfillment_order.destination.country.name
order.shipping_address.customers.referencefulfillment_order.destination.reference
order.shipping_address.customers.between_streetsfulfillment_order.destination.between_streets
order.shipping_tracking_numberfulfillment_order.tracking_info.number
order.shipping_tracking_urlfulfillment_order.tracking_info.url

Important: To maintain backwards compatibility, we will continue to maintain the shipping information in the Order, but with the values corresponding to the first Fulfillment:

  • order.shipping → will return the value of fulfillment_order.shipping.carrier_id of the first Order Fulfillment.
  • order.shipping_* → will return the corresponding value according to the table of the first Fulfillment of the Order.
  • order.shipped_at → will return the date the first Order Fulfillment was shipped.

This means that if the store does not manage stock in multiple locations, apps will continue to work without any issues. But, if the store manages its stock in multiple locations, apps may show incorrect information regarding the order.

Changes in shipping cost calculation

The shipping cost calculation API will not receive any changes. In this way, Shipping Carriers do not have to change their current implementation for calculating shipping rates.

What will change are the amounts of calls that can be received per order. If during the purchase process, a cart has to calculate the shipping costs assuming that there are products that will be fulfilled from different locations, we will send one quote for each location to each Shipping Carrier.

Changes to Fulfillment Events

Fulfillment Events that used to be sent for the Order now have to be sent for every Fulfillment Order. The object that represents the tracking status of a shipment is now called Tracking Events. We kept the same groups of resources, to obtain, create, update and delete Fulfillment Orders Tracking Events.

See the example of equivalence between the APIs:

POST /orders/{id}/fulfillments

{
"status": "delivered",
"description": "Objeto entregue ao destinatário",
"city": "São Paulo",
"province": "São Paulo",
"country": "BR",
"happened_at": "2013-04-22T11:39:12-03:00",
"estimated_delivery_at": "2013-04-22T11:39:12-03:00"
}

To POST /orders/{id}/fulfillment-orders/{id}/tracking-events

{
"status": "delivered",
"description": "Objeto entregue ao destinatário",
+ "address": "Avenida Paulista, 15 São Paulo, São Paulo - Brasil",
- "city": "São Paulo",
- "province": "São Paulo",
- "country": "BR",
"happened_at": "2013-04-22T11:39:12-03:00",
"estimated_delivery_at": "2013-04-22T11:39:12-03:00",
+ "geolocation": {
+ "longitude": 73.856077,
+ "latitude": 40.848447
+ }
}

This is a list of equivalence between the attributes of Orders Fulfillment Events V1 to Fulfillment Orders Tracking Events:

Order Fulfillment Events V1Fulfillment Order Tracking Events
fulfillment_events.idfulfillment_order.tracking_events.id
fulfillment_events.statusfulfillment_order.tracking_events.status
fulfillment_events.descritpionfulfillment_order.tracking_events.description
fulfillment_events.cityfulfillment_order.tracking_events.address (non compatible type) *
fulfillment_events.provincefulfillment_order.tracking_events.address (non compatible type) *
fulfillment_events.countryfulfillment_order.tracking_events.address (non compatible type) *
created_atfulfillment_order.tracking_events.created_at
updated_atfulfillment_order.tracking_events.updated_at
happened_atfulfillment_order.tracking_events.happened_at
estimated_delivery_atfulfillment_order.tracking_events.estimated_delivery_at
non existsfulfillment_order.tracking_events.geolocation

*It's up to each application to define how the tracking address is represented as a string. The fulfillment event's city, province and country could be informed in fulfilment_order.tracking_events.address by concatenating all the information. Eg.: "Some street 31, Some City, Some State, Some Country".

This is the full feature API resources endpoints equivalency list:

Endpoint (before)Endpoint (now)
POST /orders/{id}/fulfillmentsPOST /orders/{id}/fulfillment-orders/{id}/tracking-events
GET /orders/{id}/fulfillmentsGET /orders/{id}/fulfillment-orders/{id}/tracking-events
GET /orders/{id}/fulfillments/{id}GET /orders/{id}/fulfillment-orders/{id}/tracking-events/{id}
DELETE /orders/{id}/fulfillments/{id}DELETE /orders/{id}/fulfillment-orders/id/tracking-events/{id}

Important: To maintain backwards compatibility, if Fulfillment Events are sent to the old endpoints, we will update the events of the first Fulfillment Order. This means that if the store does not manage stock in multiple locations, apps will continue to work without any issues. But, if the store manages its stock in multiple locations, apps can potentially set Fulfillment Events on the wrong Fulfillment Order. Important: Statuses for fulfillment events in the new tracking events have not been added, updated or removed.

Shipping status changes

Since the Order can now have multiple fulfillments, we are going to add two values to order.shipping_status:

  • partially_packed
  • partially_fulfilled

In turn, each Fulfillment can have these statuses in fulfillment_order.status:

  • unpacked: The fulfillment order was ready for packing. Same as in progress or fulfillment started.
  • packed: The fulfillment order was packed, same as ready for sending.
  • ready_for_pickup: The fulfillment order was ready for pickup, same as ready for pickup.
  • dispatched: The fulfillment order was sent. Just for API.
  • marked_as_fulfilled: The fulfillment order was sent. Just for Manual.
  • delivered: The fulfillment order was fully fulfilled.

And now each order.shipping_status means the following:

  • unpacked: If there is no Fulfillment Order with a packed status.
  • partially_packed: If at least one Fulfillment Order reached packed status (but not all!) and none reached dispatched.
  • packed: If all the Fulfillment Order reached packed status and none reached dispatched.
  • partially_fulfilled: If at least one Fulfillment reached dispatched status.
  • fulfilled: If all the Fulfillment Order reached dispatched status.

More details can be seen in the documentation for Order and Fulfillment Order.

Changes in how to inform shipping status changes for the order

Shipping status on an Order now have to be informed on each Fulfillment Order.

Endpoint (before)Endpoint (now)
POST /orders/{id}/packPATCH /orders/{order_id}/fulfillment-orders/{fulfillment_order_id}
POST /orders/{id}/fulfillPATCH /orders/{order_id}/fulfillment-orders/{fulfillment_order_id}

The fulfillment order was packed

{
"status": "PACKED"
}

The fulfillment order was sent

{
"status": "DISPATCHED",
"tracking_info": { "code": "BR123123123AA", "url": "https://www.correios.com.br/BB123123123AA"}
}

Important: To maintain backwards compatibility, if shipping status changes for the order are sent to the old endpoints, we will apply the changes only in the first Fulfillment Order. This means that if the store does not manage stock in multiple locations, apps will continue to work without any issues. But, if the store manages its stock in multiple locations, apps can potentially change the shipping status of the wrong Fulfillment Order.

Changes in invoices

An Order may have more than one invoice, so we will have a Metafield that holds a list of invoices. It's the responsbility of invoice producers to make sure this format is maintained when new invoices are added.

More details can be found here.

New scopes

New read_fulfillment_orders/write_fulfillment_orders permissions are needed to be able to access endpoints with prefix /orders/{id}/fulfillment-orders/{id}.

Important: Apps will automatically receive the scope read_fulfillment_orders on all merchants' access_tokens if they have the read_orders scope. IDEM for write_fulfillment_orders.

Integration guides

To simplify the adaptation process, we are going to create the following guides to migrate the most common use cases that we will complete as they are ready:

What happens if I decide not to adapt?

Unfortunately, there are apps that, although they will not stop working when we launch the possibility of managing multi inventory stock, most likely will not work correctly for stores that have more than one location in their store.

Let's see the example of an application that reports the tracking codes of a shipment. Currently, the tracking code was sent in the POST /orders/{id}/fulfill payload. When we launch the possibility of managing multi inventory stock, the correct way to send the tracking code is going to be calling POST /orders/{id}/fulfillment-orders/{id}/fulfill sending the tracking code in the payload. As the order can potentially have multiple shipments, we need to know which shipment to associate the tracking code with. Let's imagine an order that has three shipments from three different locations and we receive POST /orders/{id}/fulfill with a tracking code, which shipment do we assign it to? For apps not to stop working, we have to assign it to one Fulfillment Order and we decided to assign it to the first one. If the store does not have multiple locations, it is not a problem because there can only be one shipment per sale. But if the order has more than one location, the tracking code sent to POST /orders/{id}/fulfill may have been intended to a fulfillment other than the first, causing confusion for both merchants and consumers.

This is why we decided to introduce the concept of "multi inventory ready" apps.

The conditions to be "multi inventory ready" is to meet any of the following requirements:

  • Do not use any of these scopes:
    * read_products
    * write_products
    * read_orders
    * write_orders
    * read_draft_orders
    * write_draft_orders
    * read_shipping
    * write_shipping
  • Use any of the scopes above and:
    * Have passed an automatic approval of multi inventory.
    * In the event that we so decide, pass a manual multi inventory approval.

Automatic approval requires that the application does not use deprecated endpoints or existing endpoints with deprecated parameters within the last 14 days.

Manual approval requires having passed the automatic approval and then passing a comprehensive re-approval — similar to the one carried out when the application was published — where we validate that the application is multi inventory ready.

The list of deprecated endpoints is:

  • POST /orders/{id}/pack
  • POST /orders/{id}/fulfill
  • POST /orders/{id}/fulfillments
  • GET /orders/{id}/fulfillments/{id}
  • DELETE /orders/{id}/fulfillments/{id}

And the list of deprecated parameters of existing endpoints is:

EndpointsParámetros
POST /products
PUT /products/{id}
POST /products/{id}/variants
PUT /products/{id}/variants/{id}
PATCH /products/{id}/variants
POST /products/{id}/variants/stock
variant.stock
POST /orders/
POST /draft_orders/
shipping_carrier_name
shipping_tracking_number
shipping_tracking_url
shipping
shipping_option
shipping_option_code
shipping_option_reference
shipping_pickup_details
shipping_store_branch_name
shipping_pickup_type
shipping_suboption

Knowing which apps are ”multi inventory ready”, we will give visibility to merchants in two key moments:

  • When they are going to add their second location — i.e. turn on multi inventory location.
  • If they already have more than one location and decide to install a new app.

In the first case, if there is an app installed that is not “multi inventory ready” we are going to give merchants visibility and ask them if they want to proceed adding the location or contact the developer of the app to make the necessary adjustments — this could mean that merchants decide to uninstall apps to be able to work with multi inventory stock.

In the second case, when installing the app we are going to give merchants visibility that the app is not "multi inventory ready" and let them decide if they want to proceed with the app install.

How is the timeline for multi inventory stock

This is the timeline in which we plan to make the different deliveries available so that you can plan ahead.

DeliverableETA
New Location endpointsJanuary 2023
Integration guides for specific use casesFebruary 2023
Mocks for new endpoints and paramentersFebruary 2023
Product y Product_Variant API compatibility for multi inventory stockMarch 2023
Order API compatibility for multi inventory stockMarch 2023
New Fulfillment Order endpointsMarch 2023
Own internal tool to track automatic approval for appsApril 2023

Important: These are all estimates and are subjected to changes. We'll keep this guide up to date to reflect the current estimation at all times.

What are we adding in the future

After launching multi inventory stock we intend to add the following improvements:

  • Webhooks for Location and Inventory_Level.
  • Allow apps to subscribe to an endpoint to define where they want the stock for each order to come from before calculating shipping rates.
  • Location object for layouts.