Using the API
Errors
One error envelope and a small published code set, on every endpoint. Branch on
code, never on message.
The error envelope
Every failure, on every endpoint, is:
{
"error": {
"code": "not_found",
"message": "no such product in this account",
"request_id": "req_8f3a…"
}
}
code— the machine-readable reason. This is the field to branch on.message— a human-readable explanation. It can change at any time; never parse or branch on it.request_id— correlation id (also in theX-Request-Idheader). Quote it when contacting support.
Published error codes
The code set only ever grows. Treat an unknown code as a generic failure of its HTTP status — the versioning promise guarantees no existing code changes meaning.
| Code | Status | Meaning |
|---|---|---|
unauthorized | 401 | No credential, or one that no longer resolves (e.g. a revoked key). |
forbidden | 403 | The credential has no authority here — a browser/Shopify session on the public API, or an API key outside /api/v1. |
forbidden_scope | 403 | The key is valid but lacks the route's required scope — a read-only key on a pim:write endpoint, or any key at all on the reserved pim:delete endpoint. |
not_found | 404 | No such record in this account. Another account's record, an id of a different entity kind, and an entity with no version on the requested store all answer identically. |
validation_failed | 400 | A parameter or body was understood but rejected — a malformed updated_since, a foreign cursor, an unknown status or scan dimension. |
rate_limited | 429 | Over the per-key or per-account minute budget. Wait Retry-After seconds — budgets and headers on Rate limits. |
pim_locked | 423 | Writes are held while an import or store refresh is being reconciled. Reachable since the data-health write endpoints shipped — retry after the merchant finishes the review. |
conflict | 409 | A stale draft_revision on a write. Part of v1's write surface; the current data-health batches re-verify state server-side instead of taking a draft_revision, so today you are most likely to meet it on future entity writes. |
internal_error | 5xx | Something failed on our side. Safe to retry with backoff; report persistent ones with the request_id. |
When Shopify is the failure: 502
The sales-channel reads query Shopify live
rather than Peak PIM's own rows. They are the only endpoints that can answer
502 — the store could not be reached — and they carry Shopify's
latency rather than ours. A 502 uses the same envelope; treat it
like a 5xx and retry with backoff.
Correlate with X-Request-Id
Every response — success or failure — carries an X-Request-Id header,
and every error body repeats it as request_id. Log it alongside your
own request logging, and quote it when reporting a problem: it lets support find
the exact request on our side.
The per-key and per-account budgets, the X-RateLimit-* headers, and the full retry guidance now live on Rate limits.