Inventory API endpoints
Products, stock, orders and invoices, as JSON.
Every list takes a cursor and an updated_since filter. Paths are relative to the base URL.
Works todayThe REST API works today on every plan, with webhooks and the MCP server.
Inventory API resources
16 resources, one shape.
Each record has an id and its fields, with money as a decimal string such as "8.50". Lists return data, has_more and next_cursor.
Products and variants
- GET /productsList products.
- GET /products/{id}Get one product.
- POST /productsCreate a product.
- PATCH /products/{id}Update a product.
- GET /products/{id}/variantsList a product's variants.
- POST /products/{id}/variantsAdd a variant.
- GET /variantsList variants; filter by sku or barcode.
- GET /variants/{id}Get one variant.
- PATCH /variants/{id}Update a variant.
Locations
- GET /locationsList locations.
- GET /locations/{id}Get one location.
- POST /locationsCreate a location.
- PATCH /locations/{id}Update a location.
Stock levels
- GET /stock_levelsList stock on hand by variant and location; filter by sku, variant_id or location_id.
- GET /stock_levels/{variant_id}Get one variant at every location. below_reorder_point compares available plus on order with the reorder point.
Stock movements
- GET /stock_movementsList movements, newest first.
- POST /stock_adjustmentsAdjust stock, up to 250 lines, each with a reason.
- POST /transfersMove stock between two locations. Moving reserved units needs allow_reserved: true, else 422.
- POST /stock_countsSet stock at a location to what was counted. Each difference posts as an adjustment with the reason count.
Suppliers
- GET /suppliersList suppliers.
- GET /suppliers/{id}Get one supplier.
- POST /suppliersCreate a supplier.
- PATCH /suppliers/{id}Update a supplier.
Purchase orders
- GET /purchase_ordersList purchase orders.
- GET /purchase_orders/{id}Get one purchase order.
- POST /purchase_ordersCreate a purchase order.
- PATCH /purchase_orders/{id}Update a purchase order.
- POST /purchase_orders/{id}/receiptsReceive in full or in part, with freight and duty. Receiving more than ordered needs allow_over_delivery: true, else 422.
Customers
- GET /customersList customers.
- GET /customers/{id}Get one customer.
- POST /customersCreate a customer.
- PATCH /customers/{id}Update a customer.
Price lists
- GET /price_listsList price lists.
- GET /price_lists/{id}Get one price list.
- POST /price_listsCreate a price list.
- PATCH /price_lists/{id}Update a price list.
Sales orders
- GET /sales_ordersList sales orders.
- GET /sales_orders/{id}Get one sales order.
- POST /sales_ordersCreate a sales order.
- PATCH /sales_orders/{id}Update a sales order: confirm, cancel, details, or add, change and remove lines. A line cannot go below what has shipped.
- GET /sales_orders/{id}/picksList an order's picks and unpicks, newest first: who, how and how many.
- POST /sales_orders/{id}/picksPick units on lines. No stock moves: picked units stay on hand and reserved until they ship.
- POST /sales_orders/{id}/unpickReturn picked units to unpicked, by line, or every picked unit with no lines.
- POST /sales_orders/{id}/shipmentsShip in full, or ship only what is picked (only_picked) and leave the rest open. Stock leaves here.
- POST /sales_orders/{id}/closeClose what is still open on a part-shipped order and free its reserved stock.
Invoices
- GET /invoicesList invoices; filter by status or sales_order_id.
- GET /invoices/{id}Get one invoice.
- PATCH /invoices/{id}Mark an invoice paid or void.
- POST /sales_orders/{id}/invoiceInvoice what has shipped and is not yet invoiced. Refused with 422 when there is nothing to bill.
Returns and credit notes
- POST /sales_orders/{id}/returnsRecord units sent back on shipped lines: restock at a location, or write off with a reason.
- GET /returnsList returns; filter by sales_order_id.
- GET /returns/{id}Get one return.
- POST /invoices/{id}/credit_notesCredit an invoice for a return's units at its prices and tax, or for an amount before tax.
- GET /credit_notesList credit notes; filter by invoice_id.
- GET /credit_notes/{id}Get one credit note.
Business details
- GET /workspaceGet the name, address and tax number on your documents.
Audit trail
- GET /audit_entriesList every stock change with before and after, newest first.
- GET /audit_entries/{id}Get one audit entry.
Integration activity log
- GET /activity_entriesList API writes and webhook deliveries with their results, newest first.
- GET /activity_entries/{id}Get one activity entry.
Reports
- GET /reports/stock_valueStock value by SKU and location, with totals, for today or an as_at day.
- GET /reports/cost_of_goodsCost of goods shipped against sales order lines from one day to another, at their cost when shipped.
- GET /reports/stock_value_journalOne month's stock value journal: opening and closing value, and each change split by kind, to check against your books.
Webhook endpoints
- GET /webhook_endpointsList your endpoints.
- POST /webhook_endpointsAdd an endpoint and pick its events.
- GET /webhook_endpoints/{id}Get one endpoint.
- DELETE /webhook_endpoints/{id}Remove an endpoint.
- POST /webhook_endpoints/{id}/testSend a signed test event of one of its events, and log the exact request.
Inventory API job actions
Receive, ship, invoice, adjust and move stock.
Each job follows the same rules as the app, so stock never goes below zero. Every write is recorded in the integration activity log.
Create a sales order with the API
Post the customer, the location and the lines.
A new order is confirmed and reserves stock at that location. Send status draft to hold it without reserving.
curl -X POST "https://slatestock.com/api/v1/sales_orders" \
-H "Authorization: Bearer wsk_example_4f7c2a9d1e8b6035" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-1187" \
-d '{"customer_id":"demo-cus-06","location_id":"demo-loc-wh","customer_reference":"DEMO-PO-1187","lines":[{"sku":"LTT-SGE","quantity":24,"unit_price":"8.50"},{"sku":"LTT-OAT","quantity":12}]}'HTTP/1.1 201 Created
Content-Type: application/json
{
"id": "0b9f2c4e-6a1d-4c8e-9f3a-5d7e2b1c8a40",
"number": "SO-10892",
"customer_id": "demo-cus-06",
"location_id": "demo-loc-wh",
"price_list_id": "demo-pl-06",
"status": "confirmed",
"part_picked": false,
"source": "api",
"channel": "wholesale",
"currency": "CAD",
"order_date": "2026-10-28",
"required_date": null,
"shipped_date": null,
"customer_reference": "DEMO-PO-1187",
"billing_address": {
"line1": "755 Placeholder Road",
"line2": "",
"city": "Halifax",
"region": "NS",
"postal_code": "A1A 1A1",
"country": "CA"
},
"shipping_address": {
"line1": "755 Placeholder Road",
"line2": "",
"city": "Halifax",
"region": "NS",
"postal_code": "A1A 1A1",
"country": "CA"
},
"notes": "",
"lines": [
{
"id": "4c2e8a1f-7b3d-4e9a-8c6f-1a2b3c4d5e61",
"line_number": 1,
"variant_id": "demo-var-0001",
"sku": "LTT-SGE",
"description": "Linen tea towel, Sage",
"quantity": 24,
"shipped_quantity": 0,
"picked_quantity": 0,
"reserved": 24,
"unit_price": "8.50",
"discount_percent": 0,
"tax_rate": 14
},
{
"id": "4c2e8a1f-7b3d-4e9a-8c6f-1a2b3c4d5e62",
"line_number": 2,
"variant_id": "demo-var-0002",
"sku": "LTT-OAT",
"description": "Linen tea towel, Oat",
"quantity": 12,
"shipped_quantity": 0,
"picked_quantity": 0,
"reserved": 12,
"unit_price": "8.55",
"discount_percent": 0,
"tax_rate": 14
}
],
"subtotal": "306.60",
"tax": "42.92",
"total": "349.52",
"created_at": "2026-10-28T10:02:00.000Z",
"updated_at": "2026-10-28T10:02:00.000Z"
}Receive a purchase order with the API
Post the units that arrived, with freight and duty if any.
Receiving more than a line still needs is refused with a 422. Send allow_over_delivery to take it; the line then shows over_delivered_quantity.
curl -X POST "https://slatestock.com/api/v1/purchase_orders/demo-po-1034/receipts" \
-H "Authorization: Bearer wsk_example_4f7c2a9d1e8b6035" \
-H "Content-Type: application/json" \
-d '{"lines":[{"sku":"WTH-CLY","quantity":14}]}'HTTP/1.1 422 Unprocessable Content
Content-Type: application/json
{
"error": {
"code": "validation_failed",
"message": "Over-delivery: PO-1034 has 12 left to receive of WTH-CLY, not 14. Send allow_over_delivery: true to receive the extra 2 units.",
"field": "allow_over_delivery"
}
}curl -X POST "https://slatestock.com/api/v1/purchase_orders/demo-po-1034/receipts" \
-H "Authorization: Bearer wsk_example_4f7c2a9d1e8b6035" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: receipt-1034-1" \
-d '{"lines":[{"sku":"WTH-CLY","quantity":14}],"allow_over_delivery":true}'HTTP/1.1 201 Created
Content-Type: application/json
{
"purchase_order": {
"id": "demo-po-1034",
"number": "PO-1034",
"status": "partly_received",
"lines": [
{
"id": "7d3a9c2e-1f4b-4a8e-9c6d-2b5e8f1a3c75",
"sku": "WTH-CLY",
"quantity": 12,
"received_quantity": 14,
"over_delivered_quantity": 2
}
]
},
"movements": [
{
"kind": "receipt",
"sku": "WTH-CLY",
"location_id": "demo-loc-wh",
"quantity": 14
}
]
}Inventory API filters
Filter a list by SKU, status or id.
Each list takes its own filters as well as limit, cursor and updated_since. Any other query parameter gets a 400 naming the ones it takes.
curl "https://slatestock.com/api/v1/stock_levels?sku=LTT-SGE" \
-H "Authorization: Bearer wsk_example_4f7c2a9d1e8b6035"curl "https://slatestock.com/api/v1/stock_levels?skus=LTT-SGE" \
-H "Authorization: Bearer wsk_example_4f7c2a9d1e8b6035"HTTP/1.1 400 Bad Request
Content-Type: application/json
{
"error": {
"code": "bad_request",
"message": "Unknown query parameter skus. GET /stock_levels takes only limit, cursor, updated_since, location_id, variant_id, sku.",
"field": "skus"
}
}Inventory API request fields
A misspelled field is refused, not ignored.
Each write takes only the fields the OpenAPI file lists for it. Any other field gets a 422 naming the ones it takes.
curl -X PATCH "https://slatestock.com/api/v1/variants/demo-var-0001" \
-H "Authorization: Bearer wsk_example_4f7c2a9d1e8b6035" \
-H "Content-Type: application/json" \
-d '{"price":"9.80"}'HTTP/1.1 422 Unprocessable Content
Content-Type: application/json
{
"error": {
"code": "validation_failed",
"message": "Unknown field price. PATCH /variants/{id} takes only sku, name, barcode, option_values, unit, unit_cost, list_price, reorder_point, reorder_quantity, supplier_id, supplier_sku, supplier_cost, archived.",
"field": "price"
}
}Edit sales order lines with the API
Lines merge by line_id.
Send line_id to change a line, leave it out to add one, and send remove: true to take one off. Lines you do not send stay as they are.
- A line with line_id keeps any field you leave out, such as its quantity or price.
- A new line names its item by sku or variant_id, with a quantity.
- A line with units picked or shipped cannot be removed, or go below those units.
PATCH /sales_orders/{id}
{
"lines": [
{
"line_id": "4c2e8a1f-7b3d-4e9a-8c6f-1a2b3c4d5e61",
"quantity": 30
},
{
"line_id": "4c2e8a1f-7b3d-4e9a-8c6f-1a2b3c4d5e62",
"remove": true
},
{
"sku": "LTT-OAT",
"quantity": 6
}
]
}Sales order statuses in the API
One status, the same in the app, CSV, MCP and webhooks.
The API sends the value and the app shows the label. The value cancelled keeps its API spelling; the app shows Canceled.
| Value | In the app | Meaning |
|---|---|---|
draft | Draft | Held; it reserves nothing. |
confirmed | Confirmed | Reserves stock; nothing picked yet. |
part_picked | Part picked | Some units picked, some still to pick. |
picked | Picked | Every unit still to ship is picked. |
part_shipped | Part shipped | Part shipped, and no open unit is short. |
backordered | Backordered | Some open units are short: open orders reserve more than the ship-from location has on hand. |
shipped | Shipped | Everything shipped or closed, and not yet fully invoiced. |
invoiced | Invoiced | Shipped, and every shipped unit invoiced. |
cancelled | Canceled | Canceled before anything shipped. |
Filter with status, such as status=part_shipped,backordered. The part_picked flag stays, true exactly when status is part_picked.
Find a sales order by reference
Look up an order by its Shopify number.
Filter sales orders by customer_reference for an exact match. Or send reject_duplicate_reference: true, and a second order with that reference gets a 409 naming the first.
curl "https://slatestock.com/api/v1/sales_orders?customer_reference=PO%201221" \
-H "Authorization: Bearer wsk_example_4f7c2a9d1e8b6035"curl -X POST "https://slatestock.com/api/v1/sales_orders" \
-H "Authorization: Bearer wsk_example_4f7c2a9d1e8b6035" \
-H "Content-Type: application/json" \
-d '{"customer_id":"demo-cus-06","customer_reference":"PO 1221","reject_duplicate_reference":true,"lines":[{"sku":"LTT-SGE","quantity":6}]}'HTTP/1.1 409 Conflict
Content-Type: application/json
{
"error": {
"code": "duplicate_reference",
"message": "SO-10004 (id demo-so-10004) already has customer_reference PO 1221.",
"field": "customer_reference"
}
}Inventory valuation API
Stock value and cost of goods in one request.
Unit cost is a weighted average that includes landed freight and duty. Each report comes back whole, with no pages.
curl "https://slatestock.com/api/v1/reports/stock_value?as_at=2026-09-30" \
-H "Authorization: Bearer wsk_example_4f7c2a9d1e8b6035"HTTP/1.1 200 OK
Content-Type: application/json
{
"as_at": "2026-09-30",
"currency": "CAD",
"costing_method": {
"id": "weighted_average_landed",
"description": "Unit cost is a weighted average that includes landed freight and duty."
},
"total_units": 24575,
"total_value": "478963.33",
"locations": [
{
"location_id": "demo-loc-wh",
"name": "Main warehouse",
"units": 15893,
"value": "337155.03"
},
{
"location_id": "demo-loc-ovf",
"name": "Overflow store",
"units": 5860,
"value": "88124.61"
},
{
"location_id": "demo-loc-shop1",
"name": "Downtown shop",
"units": 685,
"value": "14381.51"
},
{
"location_id": "demo-loc-shop2",
"name": "Lakeside shop",
"units": 706,
"value": "12476.47"
},
{
"location_id": "demo-loc-3pl",
"name": "3PL (Example Fulfillment)",
"units": 1431,
"value": "26825.71"
}
],
"data": [
{
"variant_id": "demo-var-0103",
"sku": "BMT-CHR",
"name": "Cotton bath mat, Charcoal",
"location_id": "demo-loc-wh",
"on_hand": 60,
"unit_cost": "15.12",
"value": "907.20"
},
{
"variant_id": "demo-var-0103",
"sku": "BMT-CHR",
"name": "Cotton bath mat, Charcoal",
"location_id": "demo-loc-ovf",
"on_hand": 15,
"unit_cost": "15.12",
"value": "226.80"
}
]
}Inventory API errors
Standard status codes, with a JSON error code.
Send an Idempotency-Key on any write, and a retry within 24 hours gets the first answer back. Only a success is kept, so a corrected request can use the same key.
| Status | Code | Meaning |
|---|---|---|
| 400 | bad_request | The JSON or a query value could not be read, or a query parameter is unknown. |
| 401 | invalid_api_key | The key is missing, wrong or revoked. |
| 403 | read_only_key | A read-only key tried to write. |
| 403 | workspace_read_only | The workspace's plan has ended, so it can be read but not written. |
| 404 | not_found | No record with that id in this workspace. |
| 405 | method_not_allowed | The path does not take that method. |
| 409 | idempotency_key_reused | The Idempotency-Key was sent with a different request. |
| 409 | duplicate_reference | Another sales order has that customer_reference, and the request asked to refuse a duplicate. |
| 422 | validation_failed | A field is missing, unknown or out of range, or the record's status does not allow it. |
| 429 | rate_limited | Over the limit; wait for Retry-After. |
Inventory management API pricing
The API, webhooks and the MCP server come with every plan.