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 URL | https://app.peak-pim.com |
|---|---|
| Version | /api/v1 — additive changes only within v1 |
| Authentication | Authorization: Bearer pk_live_… API key (details) |
| Format | JSON over HTTPS; every failure uses one typed error envelope |
| Rate limits | 120 requests/minute per key, 240/minute per account (details) |
| Machine spec | OpenAPI 3.1 document |
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:
- Products, variants, collections, and media files — the entities in your catalog.
- Stores — the Shopify stores linked to the account.
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:
pending_publish— there are draft changes not yet pushed to the store(s).last_publish_status— one ofnever_published,pending,published,failed.draft_revision— the entity's revision counter, the optimistic-concurrency handle a future write sends back to prove it read the current state.
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
| Parameter | Meaning |
|---|---|
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
- Within v1, changes are additive only: new fields and new endpoints may appear at any time. Clients must ignore response fields they do not know.
- A field removal, retype, or semantic change would ship as
/api/v2, never inside v1. - Deprecations are announced with
DeprecationandSunsetheaders and in the changelog. - The error-code set only ever grows — treat an unknown code as a generic failure of its HTTP status.