Guides

Data health

Data health is a loop: scan a dimension of catalog quality, read the issues behind its numbers, then remediate them in draft-only batches. These are the first non-GET endpoints on v1 — and they are built so a stale batch can never clobber newer values.

The loop at a glance

POST /api/v1/data-health/scans                    # recompute one dimension    (pim:write)
GET  /api/v1/data-health                          # read the cached snapshot   (pim:read)
GET  /api/v1/data-health/issues?section=...       # the entities behind it     (pim:read)
POST /api/v1/data-health/completeness-fills       # fill empty fields, drafts  (pim:write)
POST /api/v1/data-health/divergence-reconciles    # harmonize stores, drafts   (pim:write)

Read the cached snapshot

GET/api/v1/data-healthpim:read

The account's catalog-quality state per dimension, as of the last scan. Data health is derived, re-computable state, so this read never scans — it is cheap to poll. Scope to one store with store_id; omitted, the answer is account-wide.

curl -s https://api.peak-pim.com/api/v1/data-health \
  -H "Authorization: Bearer pk_live_..."

{"dimensions": [
  {"dimension": "completeness", "scanned": true,
   "result": {...}, "scanned_at": "2026-07-30T06:00:04Z"},
  {"dimension": "divergence", "scanned": false}
]}
scanned: false is not "clean".

It means no scan has ever run for that dimension — a different answer from "scanned, and clean". Each dimension's result is an opaque object whose shape is specific to that dimension and may gain fields within v1.

Run a scan

POST/api/v1/data-health/scanspim:write

Recompute one dimension and cache the result. This is the expensive path — it scans the catalog — so it is a request, never something to poll. It touches no merchant data.

curl -s -X POST https://api.peak-pim.com/api/v1/data-health/scans \
  -H "Authorization: Bearer pk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"dimension": "completeness"}'

{"dimension": "completeness", "scanned": true,
 "result": {...}, "scanned_at": "2026-07-31T09:12:40Z"}

Add store_id to scope the scan to one store, where the dimension is store-scoped. The response is the fresh snapshot for that dimension.

Page through the issues

GET/api/v1/data-health/issuespim:read

One page of one section's issue rows — the entities behind a dimension's numbers. Unlike every other list on v1, this one is page-numbered (page, page_size defaulting to 25 and clamped to 100, has_next) rather than cursor-paged, because a section is a live query over changing state rather than a stable list to walk.

curl -s "https://api.peak-pim.com/api/v1/data-health/issues?section=completeness-required&page=1&page_size=100" \
  -H "Authorization: Bearer pk_live_..."

{"section": "completeness-required",
 "data": [
   {"entity_id": "0e8f...", "entity_type": "product", "title": "Trail Runner 2",
    "store_id": "c3d4...", "fields": ["body_html", "vendor"]}
 ],
 "page": 1, "has_next": false}

section is required and names which issue list to read — for example sync-failed, completeness-required, divergence, media-unused, or translations-untranslated. Narrow with store_id, and with locale on the translation sections. The divergence section also reports compared — how many multi-store entities the scan compared to produce the list.

Remediate: fill empty fields

POST/api/v1/data-health/completeness-fillspim:write

Write authored values into fields that are still empty, up to 500 operations per batch. Each operation names one entity, one store, one field, and the value to write:

curl -s -X POST https://api.peak-pim.com/api/v1/data-health/completeness-fills \
  -H "Authorization: Bearer pk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "entity_type": "product",
    "operations": [
      {"entity_id": "0e8f...", "store_id": "c3d4...",
       "field": "vendor", "value": "Peak Outfitters"}
    ]
  }'

{"filled": 1,
 "results": [
   {"entity_id": "0e8f...", "store_id": "c3d4...", "field": "vendor",
    "ok": true, "filled": true}
 ]}

Remediate: harmonize diverging stores

POST/api/v1/data-health/divergence-reconcilespim:write

Bring one field into agreement across stores. Each operation names an entity and a field, plus exactly one of winner_store_id (adopt that store's value) or value (write an explicit one; add store_id to narrow it to one store). Stores that already match are left untouched.

curl -s -X POST https://api.peak-pim.com/api/v1/data-health/divergence-reconciles \
  -H "Authorization: Bearer pk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "entity_type": "product",
    "operations": [
      {"entity_id": "0e8f...", "field": "title", "winner_store_id": "a1b2..."}
    ]
  }'

{"patched": 1,
 "results": [
   {"entity_id": "0e8f...", "store_id": "c3d4...", "ok": true}
 ]}

Same rules as fills: partial success is normal, writes are drafts only, and the divergence is re-checked immediately before writing. Both remediation endpoints can also answer 423 pim_locked while a store import or refresh is being reconciled — retry after the merchant finishes the review.

Reserved: unused deletions

POST/api/v1/data-health/unused-deletionspim:delete

The one destructive route is documented but reserved, not reachable: it requires pim:delete, which is not granted by the consent flow or by key minting, so it answers 403 for every credential that exists today. It is documented so the contract is honest about what the endpoint would do — delete entities across every store they live on, re-verifying each candidate as still-unused immediately before deletion.

The MCP equivalents

The connector carries the whole loop. Note the vocabulary shift: MCP tools say master_id / channel_id / winner_channel_id where REST says entity_id / store_id / winner_store_id.

ToolScopeAnswers / does
get_data_health_v1pim:readThe cached per-dimension state — sync, completeness, media, structure, cross-store divergence, translations — as of the last scan
list_data_health_issues_v1pim:readOne section's issue rows, paged with page/page_size, narrowed by channel_id and locale
run_data_health_scan_v1pim:writeRecompute one dimension and cache the result
fill_completeness_gaps_v1pim:writeBatch-fill still-empty fields as drafts (up to 500 operations)
reconcile_value_divergence_v1pim:writeBatch-harmonize a field across stores as drafts

There is no delete tool on the connector at all. After remediation, drafts go live only through an explicit publish — publish_master_v1 on the publish tier, per item.