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

GET/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

ParameterTypeDescription
limitintegerRows per page. Default 50, maximum 250; a value above the maximum is clamped and a non-numeric value falls back to the default.
cursorstringThe 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_sinceRFC3339 date-timeOnly 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.
statusenumPublish-state filter: never_published, pending, published, failed. This is where the product stands with Peak PIM's stores, not Shopify's own product status.
searchstringFree-text match on the product's name, store title, vendor, tags, or a variant SKU.

Response

FieldTypeDescription
data[].iduuidThe product's id.
data[].typestringAlways product on this list.
data[].titlestringThe product's name.
data[].draft_revisionintegerThe entity's revision — the optimistic-concurrency handle a later write sends back to prove it read the current state.
data[].pending_publishbooleanDraft changes not yet pushed to a store.
data[].last_publish_statusenumnever_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_idsuuid[]The stores the product lives on.
next_cursorstring or nullPass 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"
}
updated_since watches the canonical record only.

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

GET/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.

FieldTypeDescription
id, type, titleAs on the list row; type is product.
attributesarrayThe canonical values every store inherits, as {key, value, type} triples (all strings).
draft_revision, pending_publish, last_publish_statusAs on the list row.
variant_idsuuid[]The product's variants. Fetch each via GET /api/v1/variants/{id}.
media_idsuuid[]The media files this product uses, in gallery order, across every store.
stores[]arrayPublish 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

GET/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

GET/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:

EndpointReturnsDocs
GET /api/v1/products/{id}/translationsEvery 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}/dropsThe drops that will change this product (or its variants), each narrowed to the changes that touch it.Drops