Peak PIM Public API

The versioned, supported surface third-party systems program against. Read your whole catalog — products, variants, collections, media files, linked stores, and publish state — with plain HTTPS and JSON.

Base URLhttps://app.peak-pim.com
Version/api/v1 — additive changes only within v1
AuthenticationAuthorization: Bearer pk_live_… API key (details)
FormatJSON over HTTPS; every failure uses one typed error envelope
Rate limits120 requests/minute per key, 240/minute per account (details)
Machine specOpenAPI 3.1 document
v1 today is a read API.

Every published endpoint is a read. Write and publish scopes already exist on API keys, and write endpoints will arrive additively within v1 — the error codes they use (conflict, pim_locked) are already part of the published set so your error handling won't change.

The data model in one minute

A Peak PIM account owns a catalog shared by one or more linked Shopify stores. The API speaks in five nouns:

Every entity's values exist twice: the canonical values that every store inherits, and one store version per store that can override them. GET /api/v1/products/{id} returns the canonical side; GET /api/v1/products/{id}/stores/{store_id} returns what one store actually publishes.

Values are carried as attributes: flexible {key, value, type} triples, on the entity and on each store version. Relationships come as id arrays — a product's variant_ids and media_ids, a variant's product_id.

Publish state

Peak PIM tracks where every draft stands with Shopify. Entities and store versions both carry:

Pagination

List endpoints return one page at a time: {"data": [...], "next_cursor": "..."}. Pass next_cursor back as cursor for the next page; it is null on the last page. Cursors are opaque — never build or parse one, and a cursor from a different list is rejected with validation_failed rather than silently skipping rows.

limit defaults to 50 and is clamped at 250. Lists are ordered oldest change first, so a poller can walk the whole catalog and stop when next_cursor is null. Two lists are bounded and never paginate: /stores and the per-entity /stores subresources always return everything with next_cursor: null.

Filtering

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.
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).
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.

Compatibility promise

Next steps