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.
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.
Request
Replaces the purchase order's own fields.
- Any nullable field you omit or send as
nullis cleared, so fetch the order first and send it back with your changes. line_itemsreplaces the whole list: a line with anidis updated, a line without one is added, and any stored line you leave out is removed.- On a line that already exists you cannot change
item_id,nameorvariant_options, and lines cannot be reordered. - Only a
draft,ready_for_revieworapprovedorder can be updated. Anything later returns400. - Use the status endpoint to change status.
Order-level charges as decimal strings. Each defaults to 0. total is sub_total + tax + shipping_cost - discount, so a discount larger than the rest of the order is rejected with 400.
The purchase order's lines after the update, up to 100. An existing line left out is removed; an empty array removes every line.
ID of an existing line on this purchase order. Omit to add a new line. An id that is not on this purchase order returns 400.
ID of the Sortly item to order. Required when you add a line. On a line that already exists send it back unchanged, including null if the item was deleted.
Up to 3 options, and only settable when you add a line. Leave them out on a line that already exists, or send them back exactly as you read them.
- https://api.sortly.co/api/v1/purchase_orders/{purchase_order_id}
- curl
- JavaScript
- Node.js
- Python
- Java
- C#
- PHP
- Go
- Ruby
- R
- Payload
curl -i -X PUT \
https://api.sortly.co/api/v1/purchase_orders/506 \
-H 'Authorization: Bearer <YOUR_TOKEN_HERE>' \
-H 'Content-Type: application/json' \
-d '{
"purchase_order_number": "PO-2026-0042",
"currency_code": "USD",
"vendor": {
"name": "Ace Plumbing Supply",
"email": "orders@aceplumbing.example",
"line1": "567 Main St",
"city": "San Francisco",
"state": "CA",
"zip_code": "94106",
"country": "US",
"phone": "+14155550100"
},
"notes": "Deliver to the rear entrance. Call on arrival.",
"terms": "Net 30",
"charges": {
"tax": "2.50",
"shipping_cost": "3.00",
"discount": "0"
},
"expected_delivery_date": "2026-08-22T00:00:00Z",
"line_items": [
{
"id": 881,
"item_id": 204,
"name": "Copper Pipe 3/4 inch",
"item_number": "CP-075",
"quantity": "12",
"unit_rate": "12.50",
"unit_of_measure": {
"name": "ft"
},
"notes": "Match existing run"
}
],
"version": 1
}'{ "data": { "id": 506, "purchase_order_number": "PO-2026-0042", "status": "draft", "currency_code": "USD", "charges": { … }, "sub_total": "150", "total": "155.5", "line_items": [ … ], "created_by": "Jane Smith", "created_at": "2026-08-03T08:00:00.000000Z", "updated_by": "Jane Smith", "updated_at": "2026-08-03T09:15:00.000000Z", "version": 2, "vendor": { … }, "ship_to": null, "bill_to": null, "notes": "Deliver to the rear entrance. Call on arrival.", "terms": "Net 30", "expected_delivery_date": "2026-08-22T00:00:00.000000Z", "submitted_date": null, "submitted_by": null, "approved_date": null, "approved_by": null, "ordered_date": null, "ordered_by": null, "received_date": null, "received_by": null } }
Request
Moves the purchase order to a new status and leaves every other field alone.
draft,ready_for_reviewandapprovedcan each move to one another, or on toordered.- From
orderedyou can requestvoidedorclosed. Frompartially_receivedorreceived, onlyclosed. voidedandclosedare final. Anything else returns400. -receivedandpartially_receivedcannot be requested here. Receiving sets them as stock arrives, which also moves an order on fromorderedwithout a status call.- Moving to
orderedrecords who ordered it and when, and puts the order's items on order in Sortly. - Sending the status the order already has changes nothing, so a retry is safe.
One of draft, ready_for_review, approved, ordered, voided, closed. received and partially_received cannot be requested.
- https://api.sortly.co/api/v1/purchase_orders/{purchase_order_id}/status
- curl
- JavaScript
- Node.js
- Python
- Java
- C#
- PHP
- Go
- Ruby
- R
- Payload
curl -i -X PUT \
https://api.sortly.co/api/v1/purchase_orders/506/status \
-H 'Authorization: Bearer <YOUR_TOKEN_HERE>' \
-H 'Content-Type: application/json' \
-d '{
"status": "ordered",
"version": 2
}'{ "data": { "id": 506, "purchase_order_number": "PO-2026-0042", "status": "ordered", "currency_code": "USD", "charges": { … }, "sub_total": "150", "total": "155.5", "line_items": [ … ], "created_by": "Jane Smith", "created_at": "2026-08-03T08:00:00.000000Z", "updated_by": "Jane Smith", "updated_at": "2026-08-04T08:00:00.000000Z", "version": 3, "vendor": { … }, "ship_to": null, "bill_to": null, "notes": "Deliver to the rear entrance. Call on arrival.", "terms": "Net 30", "expected_delivery_date": "2026-08-22T00:00:00.000000Z", "submitted_date": null, "submitted_by": null, "approved_date": null, "approved_by": null, "ordered_date": "2026-08-04T08:00:00.000000Z", "ordered_by": "Jane Smith", "received_date": null, "received_by": null } }
Request
Records how much of the ordered quantity has arrived on the lines you name, and unless receive_config.update_strategy says otherwise adds that quantity to the matching Sortly items. Key considerations:
- The work happens in the background. A successful call returns
202straight away with the receivepending. PollGET /api/v1/purchase_orders/{purchase_order_id}/receive/statusuntil it readscompletedorfailed. - One receive at a time per order. Nothing needs carrying between the two calls; starting a second while the first is
pendingreturns409. - Lines can fail individually while the call succeeds. Read the ids out of
received_line_item_idsanderrorsrather than trusting the HTTP status. - The order must be
orderedorpartially_received. Receiving is what moves it on topartially_receivedand thenreceived, which is why neither can be set through the status endpoint. - A line must still be linked to a Sortly item, and no two lines in one request may point at the same item.
The lines being received, and how much of each has arrived. 1 to 100 lines.
id of the line being received, taken from the purchase order's line_items.
How much of this line has arrived now, on top of anything received against it before. Must be greater than zero and no more than the line's quantity less its received_quantity.
Folder to receive the items into. Defaults to wherever the item already sits.
- https://api.sortly.co/api/v1/purchase_orders/{purchase_order_id}/receive
- curl
- JavaScript
- Node.js
- Python
- Java
- C#
- PHP
- Go
- Ruby
- R
- Payload
curl -i -X POST \
https://api.sortly.co/api/v1/purchase_orders/506/receive \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <YOUR_TOKEN_HERE>' \
-H 'Content-Type: application/json' \
-d '{
"line_items": [
{
"line_item_id": 204,
"received_quantity": "12"
},
{
"line_item_id": 205,
"received_quantity": "3",
"folder_id": 87
}
],
"action": {
"reason_id": "24863b18-bd4f-4718-a46e-0e68bf969cf6",
"notes": "Delivered short, driver noted two boxes missing."
},
"receive_config": {
"update_strategy": "with_item_update"
}
}'{ "data": { "status": "pending", "received_line_item_ids": [], "pending_line_item_ids": [ … ], "errors": [], "error": null, "created_at": "2025-03-04T10:15:30Z", "updated_at": "2025-03-04T10:15:30Z" } }
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.