Catalog
Products
A product's values exist twice: the canonical values every store inherits, and one store version per store that can override them. These endpoints read both, plus the product's relationships — its variants, its media files, and where it stands with each store.
List products
/api/v1/productspim:read
One cursor page of the account's products, ordered oldest change first so a
poller can walk the whole catalog and stop when next_cursor is
null.
Query parameters
| Parameter | Type | Description |
|---|---|---|
limit | integer | Rows per page. Default 50, maximum 250; a value above the maximum is clamped and a non-numeric value falls back to the default. |
cursor | string | The next_cursor of the previous page. Opaque — do not build or parse one. A cursor from a different list is rejected with validation_failed. |
updated_since | RFC3339 date-time | Only products whose own canonical record changed at or after this time. Store-version edits do not widen it. A non-RFC3339 value is rejected with validation_failed. |
status | enum | Publish-state filter: never_published, pending, published, failed. This is where the product stands with Peak PIM's stores, not Shopify's own product status. |
search | string | Free-text match on the product's name, store title, vendor, tags, or a variant SKU. |
Response
| Field | Type | Description |
|---|---|---|
data[].id | uuid | The product's id. |
data[].type | string | Always product on this list. |
data[].title | string | The product's name. |
data[].draft_revision | integer | The entity's revision — the optimistic-concurrency handle a later write sends back to prove it read the current state. |
data[].pending_publish | boolean | Draft changes not yet pushed to a store. |
data[].last_publish_status | enum | never_published, pending, published, or failed — the same values the status filter takes, so a response value can be fed straight back into the filter. |
data[].store_ids | uuid[] | The stores the product lives on. |
next_cursor | string or null | Pass it back as cursor for the next page. Null on the last page. |
# Everything that changed since July 1, biggest pages
curl -s "https://api.peak-pim.com/api/v1/products?limit=250&updated_since=2026-07-01T00:00:00Z" \
-H "Authorization: Bearer pk_live_..."
{
"data": [
{
"id": "5e0deff8-4b6f-4a24-9d6d-2f8c2f2a9a11",
"type": "product",
"title": "Trail Runner 2",
"draft_revision": 14,
"pending_publish": true,
"last_publish_status": "published",
"store_ids": ["9b2f6c1a-7d3e-4f80-b1c5-6a4e8d2b9c30"]
}
],
"next_cursor": "eyJvZmZzZXQiOjI1MH0"
}
Editing one store's version of a product does not widen the filter — a poller that must catch store-local edits should re-read the store versions of the products it tracks. See the sync guide for the full pattern.
Get a product
/api/v1/products/{id}pim:read
One product's canonical values, its variants and media files, and where it
stands with each store it lives on. An unknown id — including a record that
belongs to another account or an id of a different kind of entity — answers
404 not_found.
| Field | Type | Description |
|---|---|---|
id, type, title | — | As on the list row; type is product. |
attributes | array | The canonical values every store inherits, as {key, value, type} triples (all strings). |
draft_revision, pending_publish, last_publish_status | — | As on the list row. |
variant_ids | uuid[] | The product's variants. Fetch each via GET /api/v1/variants/{id}. |
media_ids | uuid[] | The media files this product uses, in gallery order, across every store. |
stores[] | array | Publish state per store: store_id, store_domain, pending_publish, last_publish_status. Publish state only — the store's values live on the store subresources below. |
{
"id": "5e0deff8-4b6f-4a24-9d6d-2f8c2f2a9a11",
"type": "product",
"title": "Trail Runner 2",
"attributes": [
{ "key": "vendor", "value": "Acme", "type": "single_line_text_field" },
{ "key": "tags", "value": "trail,running", "type": "single_line_text_field" }
],
"draft_revision": 14,
"pending_publish": true,
"last_publish_status": "published",
"variant_ids": ["c7a1d2e3-5b6f-4890-a1b2-c3d4e5f60718"],
"media_ids": ["1f2e3d4c-5b6a-4798-8172-635a4b3c2d1e"],
"stores": [
{
"store_id": "9b2f6c1a-7d3e-4f80-b1c5-6a4e8d2b9c30",
"store_domain": "acme.myshopify.com",
"pending_publish": true,
"last_publish_status": "published"
}
]
}
List a product's store versions
/api/v1/products/{id}/storespim:read
Every store version of the product — the values each store actually publishes,
which may differ from every other store's. Bounded by the account's stores, so
the whole list is returned and next_cursor is always
null.
{
"data": [
{
"store_id": "9b2f6c1a-7d3e-4f80-b1c5-6a4e8d2b9c30",
"store_domain": "acme.myshopify.com",
"attributes": [
{ "key": "title", "value": "Trail Runner 2 — US Edition", "type": "single_line_text_field" }
],
"pending_publish": false,
"last_publish_status": "published"
}
],
"next_cursor": null
}
Get one store's version
/api/v1/products/{id}/stores/{store_id}pim:read
One store's version of the product. store_id comes from
GET /api/v1/stores. A product with no
version on the requested store answers 404 not_found — the same
answer as a nonexistent product.
curl -s "https://api.peak-pim.com/api/v1/products/5e0deff8-4b6f-4a24-9d6d-2f8c2f2a9a11/stores/9b2f6c1a-7d3e-4f80-b1c5-6a4e8d2b9c30" \
-H "Authorization: Bearer pk_live_..."
Subresources
Products carry three more reads, documented on their own pages:
| Endpoint | Returns | Docs |
|---|---|---|
GET /api/v1/products/{id}/translations | Every locale the product is translated into, with the translated values per store, plus the catalog of fields that can carry a translation. | Translations |
GET /api/v1/products/{id}/sales-channels?store_id=… | The Shopify sales channels the product is published to on one store. Reaches Shopify live, so it can answer 502. | Sales channels |
GET /api/v1/products/{id}/drops | The drops that will change this product (or its variants), each narrowed to the changes that touch it. | Drops |