API reference
Every v1 endpoint. Base URL https://app.peak-pim.com, bearer-key
auth, JSON responses. The machine-readable contract is the
OpenAPI 3.1 spec — this page is the same content
for humans.
Products, variants, collections, and media files share one uniform endpoint
family. For each entity type {resource} ∈
products | variants | collections | media:
| Endpoint | Returns |
|---|---|
GET /api/v1/{resource} | One cursor page of list items |
GET /api/v1/{resource}/{id} | One entity — canonical values, relationships, per-store publish state |
GET /api/v1/{resource}/{id}/stores | Every store version of the entity |
GET /api/v1/{resource}/{id}/stores/{store_id} | One store's version of the entity |
All endpoints on this page require the pim:read scope.
Service
/api/v1/ping
Verify a key. Answers 200 once the key has passed authentication,
the route allowlist, its scope, and the rate limiter — use it to check a new key
end to end before wiring real calls.
{"status":"ok","account_id_present":true}
List endpoints
/api/v1/products · /variants · /collections · /media
One cursor page of the account's entities of that type, 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; values above the maximum are clamped, non-numeric values fall 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 entities whose own (canonical) record changed at or after this time. Store-version edits do not widen it. Non-RFC3339 values are rejected with validation_failed. |
status | enum | Publish-state filter: never_published, pending, published, failed. |
search | string | Free-text match. Products: name, store title, vendor, tags, or a variant SKU. Variants: name, SKU, or parent product's name/title/vendor/tags. Collections: name or store title. Media: filename, tags, or alt text. |
Response — 200
{
"data": [
{
"id": "uuid",
"type": "product | variant | collection | media",
"title": "…",
"draft_revision": 14,
"pending_publish": true,
"last_publish_status": "never_published | pending | published | failed",
"store_ids": ["uuid", "…"]
}
],
"next_cursor": "opaque-string-or-null"
}
Media files are named by their filename, so for media search
matches the filename and tags, and title carries the filename.
Entity endpoints
/api/v1/{resource}/{id}
One entity's canonical values — the values every store inherits — plus its relationships and where it stands with each store it lives on.
Response — 200
{
"id": "uuid",
"type": "product",
"title": "…",
"attributes": [ { "key": "…", "value": "…", "type": "…" } ],
"draft_revision": 14,
"pending_publish": true,
"last_publish_status": "published",
"product_id": "uuid", // variants only — the parent product
"variant_ids": ["uuid"], // products only — the product's variants
"media_ids": ["uuid"], // gallery order, across every store; absent on media
"stores": [
{
"store_id": "uuid",
"store_domain": "acme.myshopify.com",
"pending_publish": true,
"last_publish_status": "published"
}
]
}
stores carries publish state only; a store's values live
on the store subresources below. draft_revision is the
optimistic-concurrency handle a later write sends back to prove it read the
current state.
/api/v1/{resource}/{id}/stores
Every store version of the entity. Bounded by the account's stores, so the whole
list is returned and next_cursor is always null.
{
"data": [
{
"store_id": "uuid",
"store_domain": "acme.myshopify.com",
"attributes": [ { "key": "…", "value": "…", "type": "…" } ],
"pending_publish": false,
"last_publish_status": "published"
}
],
"next_cursor": null
}
/api/v1/{resource}/{id}/stores/{store_id}
One store's version of the entity: the values that store publishes, which may
differ from every other store's. store_id comes from
GET /api/v1/stores. An entity with no
version on the requested store answers 404 not_found.
Stores
/api/v1/stores
The Shopify stores linked to the account. The whole list is returned;
next_cursor is always null.
{
"data": [
{ "id": "uuid", "domain": "acme.myshopify.com", "name": "Acme US" }
],
"next_cursor": null
}
Response headers
| Header | Meaning |
|---|---|
ETag | On every 200. Send back as If-None-Match to get a 304 instead of a body. |
X-RateLimit-Limit | Requests allowed for this key in the current minute. |
X-RateLimit-Remaining | Requests left in the current minute. |
X-RateLimit-Reset | Unix second the current minute window resets. |
X-Request-Id | Correlation id for this request — quote it in support requests. |
Retry-After | On 429 only: seconds to wait before retrying. |
Status codes
200 success · 304 not modified (conditional read) ·
400 validation failed · 401 unauthorized ·
403 forbidden / wrong scope · 404 not found ·
429 rate limited · 5xx internal error.
Full semantics and the error envelope: Errors & limits.