List Products
GET/api/v2/products
Get a paginated list of products using Spatie QueryBuilder.
products:readGrant this scope to your token under Settings → Developer → Personal Access Tokens.
Authentication: Requires Bearer token.
Supports both simple query filters (filter[field]=value) and advanced grouped filters (filter_groups tree). Default: shows non-archived products sorted by -created_at.
Allowed sorts: id, sku, name, type, barcode, mpn, hts_code, country_of_origin, unit_cost, average_cost, supplier_price, weight, length, width, height, case_quantity, case_weight, daily_average_consumption, daily_average_consumption_net_returns, min_stock_level, total_quantity_sold, total_orders, archived_at, created_at, updated_at
Default pagination: 10 per page.
The response also includes unfiltered_total, the count of rows in the base scope before any user filters are applied — used by the frontend to show export totals.
Stock UoM filters (added 2026-05-30):
filter[stock_uom_id]— by uom_id (exact, comma-separated)filter[stock_uom_code]— by UoM code (advanced text ops)filter[stock_uom_name]— by UoM display name (advanced text ops)
The response includes inventory_non_sellable — physically-present stock in non-sellable bins. It is excluded from inventory_available but is part of On Hand (On Hand = Available + Allocated + On Hold + Non-Sellable).
Also supports hts_code (Harmonized Tariff Schedule code) and country_of_origin (ISO 3166-1 alpha-2 country of manufacture) for filtering and sorting.
Sparse fieldsets (included):
Pass included a JSON array of response field names to receive only those fields. The server then loads only the data those fields need instead of the full product payload, which makes narrow lookups — resolving a barcode to a product, for example — markedly cheaper. id is always returned. Omitting the parameter returns the complete payload, unchanged.
Example: included=["id","sku","barcode","inventory_available"]
Product type: every product carries at most one leaf product type (Organization → Product Types). Rows include product_type_id, product_type_name, product_type_path and a product_type object (id, name, path, archived_at; null when the product has no type). Filter with filter[product_type_id] — ids, comma-separated; a parent type includes all of its descendants; none selects untyped products and can be combined, e.g. 7,none — or with operator filters on product_type_name / product_type_path (e.g. filter[product_type_path][starts_with]=Diapering). filter[search] also matches the type path.
Bundle figures: for products of type bundle (which hold no stock of their own), bundle_sets_possible is the number of complete sets the component stock can make, counted per warehouse and summed (in each warehouse, the lowest of each inventory-carrying component's available quantity divided by its quantity per set, never below 0; pieces in different warehouses do not make a set; nested kits are expanded to their components), and bundle_component_avg_cost is the sum of each component's average cost × quantity per set (nested bundles roll up; null when any component has no average cost). Both are null for non-bundle products and are not sortable or filterable.
Request
Responses
- 200
- 401
- 403
- 429
OK
Response Headers
Unauthenticated — the bearer token is missing, revoked, expired, or malformed. Never retry automatically; fix the credential. See the Errors guide.
Forbidden — the token lacks a required scope, the endpoint is not available to API tokens, or the user behind the token lacks the permission. A human must adjust the token scopes or user permissions; do not retry.
Rate limited — platform limit is 1,000 requests/min; individual tokens may carry lower limits. Honor the Retry-After header before retrying. See the Rate Limits guide.