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
| Stocky | Stockroom | |
|---|---|---|
| Base | https://stocky.shopifyapps.com/api/v2 | https://api.stockroom.app/v2 |
| Path | /purchase_orders.json | /purchase_orders - no .json, JSON is the only format |
| Auth | Store-Name plus Authorization: API KEY=... | Authorization: Bearer srk2_.... The key names the store, so there is no second header |
| Paging | since_id, which means "ids greater than" on suppliers and adjustments and "ids less than" on purchase orders | One opaque cursor on every list, in one direction, withhas_more to tell you when to stop |
| Envelope | A key named after the resource: {"purchase_orders": [...]} | Always data, beside next_cursor and has_more |
| Page size | limit up to 250, with undocumented per-endpoint caps | limit 1 to 250, default 50, the same on every list |
| Getting changes | Walk the ids again | updated_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
| Stocky | Stockroom | Notes |
|---|---|---|
name | name | |
company_name | company_name | |
contact_name | contact_name | |
contact_email | email | Plus email_cc, an array, which Stocky had no equivalent of |
phone | phone | |
account_number | account_number | Stockroom also keeps one per location, in location_account_numbers |
payment_terms | payment_terms | |
currency | currency | |
city, zip, province_code, country_name | address | One nested object: line1, line2, city, province, zip, country |
is_hidden | archived plus archived_at | Archive and unarchive are actions, not a field you write |
tax_type_id | taxable | Tax rates live on the order in Stockroom, per line - see below |
notes | notes |
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
| Stocky | Stockroom | Notes |
|---|---|---|
number | number | The import kept Stocky's own number |
status: draft, confirmed, archived | status plus archived | See the status map below - confirmed splits three ways |
ordered_at | ordered_at | |
expected_on | expected_at | A plain YYYY-MM-DD date in both |
received_at | received_at | Per-line in Stocky; in Stockroom that timeline is the receipts list |
supplier_order_number | supplier_order_number | |
invoice_number | reference | Not 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_on | payment_due_on | |
paid, paid_on | payment_status, paid_at, payments | Payments are rows now, so a part payment is expressible |
currency, sometimes the literal "base" | currency, always a real ISO code | Plus exchange_rate, which Stocky did not carry |
shipping, shipping_tax_type | charges | A list of typed charges - freight, duty, handling - each with how it allocates across lines |
shopify_receive_location_id | location | {"id", "name"}, the Shopify location id |
notes | notes | |
purchase_items | lines |
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.
| Stocky | Stockroom |
|---|---|
draft | draft |
confirmed, nothing received | ordered |
confirmed, some units in | partial |
confirmed, every ordered unit in | received |
archived | archived: true, beside whatever status it had reached |
| No equivalent | closed - 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
| Stocky | Stockroom | Notes |
|---|---|---|
sku, barcode | sku, barcode | |
variant, variant_gid, product_gid | variant_id, product_id, inventory_item_id | Plain Shopify integer ids, never GIDs |
product_title, variant_title | The same two | Snapshots taken when the line was written, as in Stocky |
quantity | quantity | |
received_quantity | received_quantity, outstanding_quantity, cancelled_quantity | All three are derived and read-only. Receiving is a POST of a receipt, not a write to this field |
cost_price, supplier_cost_price | unit_cost | One 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_code | supplier_sku | |
notes_for_supplier | supplier_note | Plus internal_note, which the supplier never sees |
tax_type_id, tax_rate | taxable, tax_rate_percent | Per 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
| Stocky | Stockroom | Notes |
|---|---|---|
Two endpoints: stock_adjustments.json and stock_adjustment_items.json | One: /v2/stock_adjustments, lines embedded | No item sweep to stitch back together |
stock_adjustment_reason.reason | reason plus reason_label | reason is Shopify's own enum, which is what Shopify records; reason_label is the merchant's word for it |
adjusted_at | applied_at | |
status: adjusted and everything else | status: draft, applied, cancelled | Only applied has moved any stock |
quantity, previous_quantity | delta, quantity_before, quantity_after | delta 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 own
number. 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 sweep
updated_at_min. Walking ids misses edits, which is what Stocky's shape forced on everyone. - Do not write a status. Post the action, and handle
422 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.