StockroomAPIOpen the app
Guide

Coming from Stocky

If your integration spoke to stocky.shopifyapps.com, this is where each field went.

Shopify ended Stocky access on 1 September 2026, so this is a port rather than a migration - the old endpoint is not there to read from any more. Stores that imported their Stocky history into Stockroom still have every order, receipt and adjustment; what changes is the call your code makes to reach them.

The Stockroom API is not Stocky-shaped, deliberately. The names below line up most of the time because both apps describe the same job, not because one is a rename of the other. Read Conventions before you start: paging, money and the error envelope are the parts that will actually break a port.

The call itself

StockyStockroom
Basehttps://stocky.shopifyapps.com/api/v2https://api.stockroom.app/v2
Path/purchase_orders.json/purchase_orders - no .json, JSON is the only format
AuthStore-Name plus Authorization: API KEY=...Authorization: Bearer srk2_.... The key names the store, so there is no second header
Pagingsince_id, which means "ids greater than" on suppliers and adjustments and "ids less than" on purchase ordersOne opaque cursor on every list, in one direction, withhas_more to tell you when to stop
EnvelopeA key named after the resource: {"purchase_orders": [...]}Always data, beside next_cursor and has_more
Page sizelimit up to 250, with undocumented per-endpoint capslimit 1 to 250, default 50, the same on every list
Getting changesWalk the ids againupdated_at_min on every list, or webhooks and stop polling

Ids are the other break. A Stockroom record has a Stockroom id, and a Stocky id is not a lookup key here - see finding your imported records below.

Suppliers

StockyStockroomNotes
namename
company_namecompany_name
contact_namecontact_name
contact_emailemailPlus email_cc, an array, which Stocky had no equivalent of
phonephone
account_numberaccount_numberStockroom also keeps one per location, in location_account_numbers
payment_termspayment_terms
currencycurrency
city, zip, province_code, country_nameaddressOne nested object: line1, line2, city, province, zip, country
is_hiddenarchived plus archived_atArchive and unarchive are actions, not a field you write
tax_type_idtaxableTax rates live on the order in Stockroom, per line - see below
notesnotes

What Stocky had no equivalent of, and is worth knowing exists:lead_time_days, min_order_value,free_freight_threshold, ships_direct, the reorder flags, and the matching rules (match_vendors, match_tags) that decide which products a supplier is suggested for. A merged supplier keeps a row, withmerged_into_id pointing at the survivor, so an old id never dead-ends.

Purchase orders

StockyStockroomNotes
numbernumberThe import kept Stocky's own number
status: draft, confirmed, archivedstatus plus archivedSee the status map below - confirmed splits three ways
ordered_atordered_at
expected_onexpected_atA plain YYYY-MM-DD date in both
received_atreceived_atPer-line in Stocky; in Stockroom that timeline is the receipts list
supplier_order_numbersupplier_order_number
invoice_numberreferenceNot invoice_number. Stockroom's invoice number replaces the displayed PO number, and importing Stocky's field there hijacked real numbers - so the import puts it in reference, and that is where it still is
payment_due_onpayment_due_on
paid, paid_onpayment_status, paid_at, paymentsPayments are rows now, so a part payment is expressible
currency, sometimes the literal "base"currency, always a real ISO codePlus exchange_rate, which Stocky did not carry
shipping, shipping_tax_typechargesA list of typed charges - freight, duty, handling - each with how it allocates across lines
shopify_receive_location_idlocation{"id", "name"}, the Shopify location id
notesnotes
purchase_itemslines

The status map

Stocky had three states and a boolean. Stockroom has an explicit machine, and how far a confirmed order got is part of its status rather than something you infer by summing the lines.

StockyStockroom
draftdraft
confirmed, nothing receivedordered
confirmed, some units inpartial
confirmed, every ordered unit inreceived
archivedarchived: true, beside whatever status it had reached
No equivalentclosed - an order given up on. Closing it retracts whatever was still outstanding from Shopify's incoming, which is the thing Stocky left standing forever

There is no PATCH of status. Each move is its own call - /mark_ordered, /receipts, /close,/reopen - and an illegal one is refused with422 invalid_transition rather than silently written. That is the single biggest change to a port that used to set a state field.

Lines

StockyStockroomNotes
sku, barcodesku, barcode
variant, variant_gid, product_gidvariant_id, product_id, inventory_item_idPlain Shopify integer ids, never GIDs
product_title, variant_titleThe same twoSnapshots taken when the line was written, as in Stocky
quantityquantity
received_quantityreceived_quantity, outstanding_quantity, cancelled_quantityAll three are derived and read-only. Receiving is a POST of a receipt, not a write to this field
cost_price, supplier_cost_priceunit_costOne field, in the order's currency, as a decimal string with up to eight decimal places. Stocky's pair was the store currency and the supplier's; Stockroom keeps the order in the supplier's currency and converts nothing
supplier_codesupplier_sku
notes_for_suppliersupplier_notePlus internal_note, which the supplier never sees
tax_type_id, tax_ratetaxable, tax_rate_percentPer line in both. The order's own tax block carries the default rate

Receipts, which Stocky did not have

In Stocky, receiving was a quantity and a date on the line. In Stockroom each delivery is a record: what came in, to where, on what date, against which invoice, at what cost, with the charges allocated across it. That is what makes an undo possible - a receipt is voided, and its stock movement is reversed, rather than a number being edited back down.

For a port this means one thing above all: receive by POSTing a receipt to/v2/purchase_orders/{id}/receipts, with anIdempotency-Key header, which that route requires. The idempotency section says why.

Stock adjustments

StockyStockroomNotes
Two endpoints: stock_adjustments.json and stock_adjustment_items.jsonOne: /v2/stock_adjustments, lines embeddedNo item sweep to stitch back together
stock_adjustment_reason.reasonreason plus reason_labelreason is Shopify's own enum, which is what Shopify records; reason_label is the merchant's word for it
adjusted_atapplied_at
status: adjusted and everything elsestatus: draft, applied, cancelledOnly applied has moved any stock
quantity, previous_quantitydelta, quantity_before, quantity_afterdelta is signed. Stockroom adjusts by a delta and never sets an absolute level

Applying is its own call, and it needs an Idempotency-Key - it moves stock. A draft can be edited until then.

Finding your imported records

The Stocky id is not a key in this API. Records that came from the import carrysource: "stocky", and there is no way to ask for a record by its Stocky id.

What does work, in order of how well it holds:

  • The purchase order number. The import kept Stocky's ownnumber. Where Stocky had none, the order readsSTOCKY-<stocky id>, and a clash with an existing number got-S<stocky id> appended.
  • The supplier name. Suppliers were matched by name, case-insensitively, so your own supplier mapping table probably still resolves.
  • Your Stocky invoice number, in reference - see the purchase-order table above.

The clean way to finish a port is to walkGET /v2/purchase_orders?updated_at_min= once from the beginning of time, store the Stockroom id against whatever key your system already holds, and stop matching on text afterwards.

Four habits to unlearn

  • Do not poll ids. Subscribe to webhooks, or sweepupdated_at_min. Walking ids misses edits, which is what Stocky's shape forced on everyone.
  • Do not write a status. Post the action, and handle422 invalid_transition.
  • Do not treat money as a number. Every amount is a decimal string beside a currency code, and an order's totals stay in the supplier's currency.
  • Do not retry a receive without the same key. A retried receive with a fresh key is a double receive, and a store finds that weeks later in a stocktake.