# GET /v1/inventory/{productId}

**Service:** Inventory  
**Operation:** `InventoryService_GetProductInventory`

Returns every stock entry for the given product — one per row per colour. Each entry is the stock of a single variant row (and colour), not a product total; sum the entries when you need the product's overall position. Entries carry their variant's identifying attributes, and row ids match the rows returned by the catalogue GetProduct endpoint. The product must have inventory tracking enabled; a non-tracked product returns HTTP 404.

## Parameters

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `productId` | path | string | yes | The id of the product to retrieve inventory for. Required. |
| `fields` | query | string | no | Comma-separated response fields to include, using camelCase JSON names (e.g. `orderId,total.amountMicros`). Dot paths reach into nested objects and map transparently across arrays. Paths are relative to the resource, not the response envelope; envelope keys like `nextPageToken` are always preserved. Only 2xx JSON responses are filtered; error bodies pass through unmodified. Unknown names are silently ignored. Takes precedence over `excludeFields` when both are provided. |
| `excludeFields` | query | string | no | Comma-separated response fields to exclude, using camelCase JSON names. Dot paths and array-transparency work the same as `fields`. Paths are relative to the resource, not the response envelope. Only 2xx JSON responses are filtered; error bodies pass through unmodified. Ignored when `fields` is also provided. |

## Responses

| Status | Schema | Description |
| --- | --- | --- |
| 200 | `GetProductInventoryResponse` | A successful response. |
| 400 | `Error` | The request was rejected because it was malformed — for example a badly formed id. The body is a validation-failure envelope: an overall type and message and one entry per problem (each with a stable code, a message, and a param pointing at the offending parameter). |
| 401 | `Error` | The request is missing a valid bearer token, or the token is invalid or expired. |
| 403 | `Error` | The token is valid but does not permit this action, or the resource belongs to a company the token cannot act for. The error type is permission_denied. |
| 404 | `Error` | No catalogue product with the given id exists for the authenticated company, or the product does not have inventory tracking enabled. |
| 429 | `Error` | The request was throttled (rate_limited) or exceeded a size limit (resource_exhausted). When throttled, the Retry-After header says how many seconds to wait before retrying; a resource_exhausted request will fail the same way if retried unchanged. |
| default | `Error` | Any other error. The body is the same error envelope every error uses: a short stable type identifying the kind of failure (for example "rate_limited" or "internal"), a human-readable message, and the request's idempotency key echoed back when one was supplied. |

## Returns

`GetProductInventoryResponse`

| Field | Type | Description |
| --- | --- | --- |
| `productName` | string | The product's display name. |
| `isColourTracked` | boolean | Whether the product is colour-tracked. When true, entries are split by colour; when false, there is one entry per row. |
| `entries` | StockEntry[] | The stock entries for this product — one per row per colour. |
| `entries.productId` | string | The catalogue product this entry belongs to. |
| `entries.productName` | string | The product's display name. |
| `entries.productRowId` | string | The product variant row this entry is for — the same id the catalogue product's rows carry. Look the row up there when you need more than this entry shows, such as the variant's prices or colour options. |
| `entries.colour` | string | The colour this entry is for. Empty when the product is not colour-tracked — in that case there is one entry per row. |
| `entries.thickness` | string | The variant's thickness, when its catalogue row carries one. Empty otherwise. |
| `entries.attributes` | RowAttribute[] | The attribute pairs identifying this variant, e.g. [{name:"Size", value:"A4"}] — the same attributes the catalogue product's row carries. Together with thickness and colour they label the entry; a single-row product has no attributes to distinguish, so the list may be empty. |
| `entries.attributes.name` | string | The attribute name, e.g. "Size". |
| `entries.attributes.value` | string | The attribute value, e.g. "A4". |
| `entries.onHand` | string | Quantity currently in stock (on hand). The only directly maintained value; the others are derived from it and from order/purchase data. |
| `entries.promised` | string | Quantity committed to submitted orders that have not yet been received by the customer. Computed from open order line items. |
| `entries.onTheWay` | string | Quantity on submitted purchase orders that have not yet arrived. Computed from open purchase order items. |
| `entries.available` | string | Quantity available for new orders: `onHand` minus promised. Can be negative when more stock is committed than is on hand. |

---

Source: https://developer.factory.app/reference/inventory/get-product-inventory · Factory Sales API v1
