The units available for tracking quantity: weight, length, volume and count.
Sortly API
The Sortly API lets you work with your inventory from your own systems: add and update items, keep stock levels in sync, and track what goes out on a job.
Available on the Sortly Enterprise Plan. All requests go to https://api.sortly.co over HTTPS and return JSON. Email dev-support@sortly.com with questions or issues.
New here? Start with Getting Started, then read Items and Folders so the vocabulary in the endpoint reference makes sense. Once you know what you're building, jump to the matching Use Case Guide.
When the same thing arrives again on different terms, a fresh delivery of the same chair at a new price, clone the item rather than building a second one from scratch. The clone carries the original's name, tags, custom fields and photos, and you change only what differs.
curl -X POST 'https://api.sortly.co/api/v1/items/12345/copy' \
-H 'Authorization: Bearer YOUR_SECRET_KEY' \
-H 'Content-Type: application/json' \
-d '{ "quantity": 4, "folder_id": 11 }'A clone shares the original's Sortly ID. Both records come back with the same sid and different ids, so one scan finds them both, which is the point: it is the same product in two places or on two terms. Pass "new_sid": true if you want the clone treated as a separate product with a label of its own. See Sortly IDs (SID).
Cloning does not draw down the original. Send quantity for how many the clone holds, folder_id for where it goes, and include_subtree to bring nested items with it.
Then set what actually differs, using the data.id the clone came back with:
Try it: PUT /api/v1/items/{item_id}
curl -X PUT 'https://api.sortly.co/api/v1/items/12346' \
-H 'Authorization: Bearer YOUR_SECRET_KEY' \
-H 'Content-Type: application/json' \
-d '{ "price": 349.00 }'Stock counts drift, and a recount or a manual adjustment has to be written back. The quantity field is absolute: whatever you send becomes the new count.
Try it: PUT /api/v1/items/{item_id}
curl -X PUT 'https://api.sortly.co/api/v1/items/12345' \
-H 'Authorization: Bearer YOUR_SECRET_KEY' \
-H 'Content-Type: application/json' \
-d '{ "quantity": 10 }'You get a 204 back, and nothing else on the item is touched: sending only quantity leaves the name, price, tags and photos as they were.
To move it by a relative amount, read then write. There is no increment: to add 2 to a count of 8, fetch the item, add 2 yourself, and send the total.
Try it: GET /api/v1/items/{item_id}
curl 'https://api.sortly.co/api/v1/items/12345' \
-H 'Authorization: Bearer YOUR_SECRET_KEY'
curl -X PUT 'https://api.sortly.co/api/v1/items/12345' \
-H 'Authorization: Bearer YOUR_SECRET_KEY' \
-H 'Content-Type: application/json' \
-d '{ "quantity": 10 }'Subtracting works the same way: read 10, send 8.
"quantity": "+2"does not add 2. It is read as the number 2 and sets the count to 2, with a204and no warning. The same goes for any field name you invent for the purpose, such asquantity_delta: unknown fields are dropped silently. Onlyquantityis read, and only as an absolute value.
Items carry no version field, so nothing stops two writers overwriting each other's count between the read and the write. Where that matters, prefer the calls that express intent, since both adjust quantities as a side effect of the thing you actually mean:
Stock does not stay put: a pallet is broken down across two shelves, a box goes out to a van. Move it in Sortly so the folder tree still says where things are.
curl -X POST 'https://api.sortly.co/api/v1/items/12345/move' \
-H 'Authorization: Bearer YOUR_SECRET_KEY' \
-H 'Content-Type: application/json' \
-d '{ "quantity": 4, "folder_id": 55 }'quantity is how many to move, not the new total, and folder_id is where they land.
Moving part of a quantity splits the item. Move 4 of 10 and you get two records:
- The original keeps 6 where it was
- A new record holding 4 appears in the destination, with its own
id - Both carry the same
sid, because it is still the same product in two places. See Sortly IDs (SID)
The response is the new record, so read data.id from it rather than assuming the id you sent still describes what you moved.
Move the whole quantity and the original record leaves the source folder entirely. Pass "leave_zero_quantity": true to keep a zero-count record there instead, which is worth doing when the folder is a location you still expect to restock.
Moving stock onto a job is a different call, since it also logs the usage against that job. See Sync Jobs.
Goal: keep Sortly jobs in step with the work orders in your external system, from the moment one is raised to the moment it closes, so field staff work against accurate inventory and nobody keys the same movement in twice.
A job is a work order. Creating one also creates a folder to hold the items assigned to it. Jobs move through three statuses: not_started, in_progress, completed.
Each subsection below maps to one event in your system. Your system owns the job lifecycle and drives each of these calls; where you need to see the state on the Sortly side, read it with GET /api/v1/jobs on whatever schedule suits you.
Goal: keep Sortly purchase orders in step with the accounting or ordering system you buy through, so both sides agree on what was ordered, what it costs and where it has got to.
A purchase order records what you ordered from a vendor and on what terms, with a list of line items drawn from your inventory. You don't need to send amount, sub_total, total or line_number: Sortly works them out from the lines.
How an order moves:
draft,ready_for_reviewandapprovedcan each move to one another, or on toordered- From
orderedyou can requestvoidedorclosed - From
ordered, receiving stock also moves the order topartially_receivedand thenreceived, through the receive flow rather than a status call - From
partially_receivedorreceived, the only status you can request isclosed voidedandclosedare final
For the transitions themselves, see Manage PO Statuses.
Goal: move a Sortly purchase order to match where it has got to in your own process, whether that decision was made in another system or over email.
Every transition uses the same endpoint and the same two fields: the target status and the version you got from your last read.
- A stale
versionis rejected with409. Read immediately before writing, and retry with the new one. - Sending a status the order already has changes nothing, so a retried delivery is safe.
| From | Can move to |
|---|---|
draft, ready_for_review, approved | each other, or ordered |
ordered | voided or closed |
partially_received, received | closed |
voided, closed | final |
Anything outside that returns 400. received and partially_received cannot be requested here at all: Sortly sets them when a delivery is recorded against the order, either in the app or through the receive endpoint.
Items are the things you track: stock, tools, assets, supplies. Each one has a quantity and can carry a price, photos, tags and custom fields.
Items and folders share these endpoints; type is what tells them apart. Pass "type": "item" here. For the folder side of the same endpoints, see Folders.
Folders are how you organize inventory by location, category, job, or whatever fits how you work. They can be nested, and items live inside them.
Folders use the same endpoints as items, with "type": "folder" and no quantity or price. Nest one inside another by setting parent_id; leave it out for the top level. To list a folder's contents, pass its id as folder_id when listing items.
Purchase orders record what you have ordered from a vendor and on what terms. Each one carries vendor and ship-to details plus a list of line items drawn from your inventory.
How an order moves:
draft,ready_for_reviewandapprovedcan each move to one another, or on toordered- From
orderedyou can requestvoidedorclosed - Receiving stock moves an
orderedorder topartially_receivedand thenreceived, through the receive flow rather than a status call - From
partially_receivedorreceived, the only status you can request isclosed voidedandclosedare final
Use the status endpoint for these transitions rather than the update endpoint.
Aug 5, 2026. Added Purchase Orders: list, create, fetch, update and change status.
Jul 31, 2026. Added Jobs: list, create, fetch, update, change status and delete, plus adding items to a job and returning them.
Jun 17, 2021. Added yard and gallon units of measure.
Jun 5, 2021. Added support for item variants (Item Groups).
Jan 4, 2021. Renamed attribute_value to value on custom field values (attribute_value still works). Added tags to item reads and search.
Sep 1, 2020. Added the search endpoint.
Jun 15, 2020. Added alerts.
Mar 2, 2019. First release.