Using the API

Pagination & filtering

Every list on the API is {"data": [...], "next_cursor": ...}. The four catalog lists paginate with an opaque cursor; every other list is bounded and returns everything at once; one endpoint — data-health issues — is page-numbered.

Cursor pagination

The catalog lists — /api/v1/products, /variants, /collections, /media — return one page at a time, ordered oldest change first. Pass next_cursor back as cursor for the next page; it is null on the last page, so a poller can walk the whole catalog and stop:

curl "https://api.peak-pim.com/api/v1/products?limit=250" \
  -H "Authorization: Bearer $PEAK_PIM_KEY"
# → {"data": [...], "next_cursor": "eyJv…"}

curl "https://api.peak-pim.com/api/v1/products?limit=250&cursor=eyJv..." \
  -H "Authorization: Bearer $PEAK_PIM_KEY"
# → {"data": [...], "next_cursor": null}   — last page

Bounded lists

Every other list is bounded by something small — the account's stores, the account itself, one definition, or locales × stores — so the whole set is returned in one response and next_cursor is always null. Same envelope, no walking:

The one exception: data-health issues

GET /api/v1/data-health/issues is page-numbered rather than cursor-paged, because a section is a live query over changing state rather than a stable list to walk. It takes page (default 1) and page_size (default 25, clamped to 100), and answers with page and has_next:

curl "https://api.peak-pim.com/api/v1/data-health/issues?section=...&page=2&page_size=100" \
  -H "Authorization: Bearer $PEAK_PIM_KEY"
# → {"section": "...", "data": [...], "page": 2, "has_next": false}

Details on sections and issue rows: Data health.

Filtering

The catalog lists take three filters, freely combinable:

ParameterMeaning
updated_since Only entities whose own record changed at or after this RFC3339 timestamp — the canonical, cross-store data. Editing one store's version does not widen this filter, so re-read store versions of entities you track when you need store-local changes. A non-RFC3339 value is rejected with validation_failed.
status Only entities in this publish state (never_published / pending / published / failed). This is Peak PIM's publish state, not Shopify's own product status (which is a per-store attribute value). Response values feed straight back into the filter.
search Free-text match, per entity type: products by name, store title, vendor, tags, or a variant SKU; variants by name, SKU, or the parent product's fields; collections by name or store title; media files by filename, tags, or alt text.

Conditional reads

Every 200 response carries an ETag. Send it back as If-None-Match to get a 304 Not Modified instead of a body — cheap polling for anything you fetch repeatedly:

curl -i "https://api.peak-pim.com/api/v1/products/7b2a..." \
  -H "Authorization: Bearer $PEAK_PIM_KEY" \
  -H 'If-None-Match: "W/abc123"'
# HTTP/2 304 — nothing changed, no body transferred
A daily sync in a handful of requests.

Full pages (limit=250), updated_since for incremental polls, and ETags for repeat reads together make a recurring sync nearly free — see Rate limits and the sync guide.