Inventory
Inventory in BenchKey is tracked as per-location stock levels. Each record ties one product (part) to one location and exposes the quantity on hand plus a configurable minimum threshold. Quantities are never summed across locations, each site is reported independently so callers can reason about stock at each location.
The stock_level object
Section titled “The stock_level object”{ "object": "stock_level", "product_id": 42, "product_name": "iPhone 14 Screen Assembly", "product_sku": "SCR-IP14-OEM", "product_active": true, "location_id": 1, "location_name": "Main Shop", "is_warehouse": false, "location_active": true, "qty_on_hand": 5, "min_stock": 2, "low_stock": false, "updated_at": "2025-11-15T14:30:00.000Z"}| Field | Type | Description |
|---|---|---|
product_id | integer | Unique product (inventory part) ID |
product_name | string|null | Product display name |
product_sku | string|null | SKU, or null if not set |
product_active | boolean | Whether the product is active in your catalog |
location_id | integer | Location ID |
location_name | string|null | Location display name |
is_warehouse | boolean | true if the location is a warehouse (vs. a service counter) |
location_active | boolean | Whether the location is active |
qty_on_hand | integer | Current quantity at this location |
min_stock | integer | Reorder threshold; 0 means no threshold set |
low_stock | boolean | true when min_stock > 0 and qty_on_hand ≤ min_stock |
updated_at | string | ISO-8601 timestamp of the last stock change |
List stock levels
Section titled “List stock levels”GET /api/v1/inventoryReturns a cursor-paginated list of stock level records ordered by most recently updated first. Each row represents one product at one location.
Scope required: inventory.read
Query parameters
Section titled “Query parameters”| Parameter | Type | Description |
|---|---|---|
product_id | integer | Filter to a specific product (positive integer ≤ 2,147,483,647; out-of-range values return 400 invalid_query) |
location_id | integer | Filter to a specific location (positive integer ≤ 2,147,483,647; out-of-range values return 400 invalid_query) |
low_stock | boolean | Pass true to return only rows at or below min_stock. Accepted values: true/1/false/0. Any other value returns 400 invalid_query |
active | string | Pass "all" to include stock rows for inactive (archived) products. Default is active-only. Any value other than "1" or "all" returns 400 invalid_query |
limit | integer | Page size, 1–100 (default: 20) |
cursor | string | Opaque cursor from a previous response’s next_cursor |
curl "https://app.benchkey.com/api/v1/inventory?low_stock=true&limit=20" \ -H "Authorization: Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa"Node.js
Section titled “Node.js”const res = await fetch( "https://app.benchkey.com/api/v1/inventory?low_stock=true&limit=20", { headers: { Authorization: "Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa" } });const { data, has_more, next_cursor } = await res.json();Response
Section titled “Response”{ "object": "list", "data": [ { "object": "stock_level", "product_id": 42, "product_name": "iPhone 14 Screen Assembly", "product_sku": "SCR-IP14-OEM", "product_active": true, "location_id": 1, "location_name": "Main Shop", "is_warehouse": false, "location_active": true, "qty_on_hand": 1, "min_stock": 2, "low_stock": true, "updated_at": "2025-11-15T14:30:00.000Z" } ], "has_more": false, "next_cursor": null}See Pagination for how to page through results.
List stock by location for a product
Section titled “List stock by location for a product”GET /api/v1/inventory/:product_id/locationsReturns all location stock rows for a single product. Useful for getting a complete picture of where a product is stocked and in what quantities.
Scope required: inventory.read
curl "https://app.benchkey.com/api/v1/inventory/42/locations" \ -H "Authorization: Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa"Node.js
Section titled “Node.js”const res = await fetch( "https://app.benchkey.com/api/v1/inventory/42/locations", { headers: { Authorization: "Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa" } });const { data } = await res.json();Response
Section titled “Response”{ "object": "list", "data": [ { "object": "stock_level", "product_id": 42, "product_name": "iPhone 14 Screen Assembly", "product_sku": "SCR-IP14-OEM", "product_active": true, "location_id": 1, "location_name": "Main Shop", "is_warehouse": false, "location_active": true, "qty_on_hand": 1, "min_stock": 2, "low_stock": true, "updated_at": "2025-11-15T14:30:00.000Z" }, { "object": "stock_level", "product_id": 42, "product_name": "iPhone 14 Screen Assembly", "product_sku": "SCR-IP14-OEM", "product_active": true, "location_id": 2, "location_name": "Warehouse", "is_warehouse": true, "location_active": true, "qty_on_hand": 14, "min_stock": 5, "low_stock": false, "updated_at": "2025-11-10T09:00:00.000Z" } ], "has_more": false, "next_cursor": null}Retrieve a stock level
Section titled “Retrieve a stock level”GET /api/v1/inventory/:product_id/locations/:location_idReturns the stock level for a specific product at a specific location.
Scope required: inventory.read
curl "https://app.benchkey.com/api/v1/inventory/42/locations/1" \ -H "Authorization: Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa"Node.js
Section titled “Node.js”const res = await fetch( "https://app.benchkey.com/api/v1/inventory/42/locations/1", { headers: { Authorization: "Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa" } });const level = await res.json();Response
Section titled “Response”Returns a single stock_level object. Returns 404 if the product or location doesn’t exist.
Adjust stock
Section titled “Adjust stock”POST /api/v1/inventory/:product_id/adjustApplies a delta adjustment to a product’s stock quantity and records it in the audit log. Positive values add stock; negative values deduct. An adjustment of zero is rejected.
Scope required: inventory.write
Request body
Section titled “Request body”| Field | Required | Description |
|---|---|---|
qty_change | yes | Integer delta, positive adds stock, negative deducts |
location_id | no | Location to adjust; omit to use the default location |
reason | no | Audit log note (e.g. "Cycle count correction") |
# Deduct 2 units from location 1 (shrinkage/breakage)curl -X POST "https://app.benchkey.com/api/v1/inventory/42/adjust" \ -H "Authorization: Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa" \ -H "Content-Type: application/json" \ -d '{ "qty_change": -2, "location_id": 1, "reason": "Screen cracked during install" }'Node.js
Section titled “Node.js”const res = await fetch( "https://app.benchkey.com/api/v1/inventory/42/adjust", { method: "POST", headers: { Authorization: "Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa", "Content-Type": "application/json", }, body: JSON.stringify({ qty_change: -2, location_id: 1, reason: "Screen cracked during install", }), });const level = await res.json();Response
Section titled “Response”Returns the updated stock_level object for the adjusted location.
Error codes
Section titled “Error codes”| HTTP status | Code | Meaning |
|---|---|---|
400 | invalid_id | A path segment (product_id or location_id) is not a valid positive integer |
400 | invalid_query | low_stock is not a recognized boolean value (true/1/false/0) |
400 | invalid_param | A list query filter (product_id or location_id) is not a valid positive integer |
400 | invalid_cursor | cursor is malformed |
400 | missing_field | qty_change is absent from the request body |
400 | invalid_field | qty_change is zero or not an integer; reason is not a string; or location_id in the body is not a positive integer |
404 | not_found | Product or location does not exist |
409 | insufficient_qty | Deduction would push qty_on_hand below zero |
422 | adjust_failed | The stock adjustment failed for another reason |
403 | insufficient_scope | API key lacks inventory.read or inventory.write |
See Errors for the full error envelope format.