Discover duplicates
Use credentials for the intended organization. Reading requirescatalog:read;
refreshing a scan and merging require catalog:write.
data.status until completed with a bounded wait/backoff; stop and inspect
errorMessage on failed. idle means a scan has not been saved. Results are
from the latest saved scan; requestedAt and computedAt show its freshness.
The shared UI scan currently examines at most the 5,000 most recently updated
non-archived root products. Completion does not establish full coverage of a
larger catalog.
List rows are in items. Each row’s duplicateMatches includes peer productId,
name, SKU, score and match reasons. Use the root id plus its exact_sku peer IDs
as candidate targets, removing repeats. Follow pagination.nextCursor and
nextOffset until hasMore is false for the selected scope. A single page or
representative row is not the entire catalog. detail=summary omits duplicate
evidence; use full. duplicateStatus=active restricts discovery to active products;
omit it to include drafts as in the UI.
duplicateType=content and variants expose other UI discovery filters, but
matching content/model signals alone do not make products eligible for this
merge. The server partitions and validates exact-SKU groups. Send 2–20 distinct
products per request, at most 10 groups. Do not silently truncate larger groups.
Choose and review the master
data.groups[].products for names, SKUs, currency-labeled prices, stock
and every per-store listing. Inspect each group’s masterSelections:
A domain block returns HTTP 200 with
ok: false; the preview and eligibility are
still in data. In particular, errorCode: stock_source_requires_master means the
current master choice cannot preserve stock ownership. Choose an allowed master
and preview again. Other blockers, such as running jobs, variants, bundles,
currency differences and same-store listing conflicts, remain enforced.
--dry-run only prints the request and
does not perform server validation. Review each successful group’s productIds
(master first), sku and preserved listings before approving a merge.
The UI dialog’s Dry run button opens chat with the selected products and
master. Choose Run dry run in the tool card. The server runs the same merge
transaction, validates database constraints and rolls back its writes. Products,
marketplace links, references and audit records remain unchanged; it emits no
product-update events or duplicate scan. A real merge needs a separate approval.
CLI and SDK callers use dryRun: true on the merge operation for this server-side
simulation. This differs from the CLI’s transport-only --dry-run option:
dryRun: true; counts and per-product results describe
validation outcomes. mergedProductIds is empty and wouldMergeProductIds
contains the sources that passed simulation. Partial failures remain explicit.
Merge the reviewed groups
PassexpectedGroups from the successful preview. This pins group membership,
master and SKU; changed or omitted groups fail rather than being silently merged.
data.success,
succeededCount, failedCount, groups and ordered per-product items. Groups
commit independently; report partial failures. Do not treat HTTP 200 as proof that
every product merged. The server rechecks stock connections and other constraints
inside the merge transaction. A separate authorized publish/update is needed to
send content or prices to existing marketplace listings.
TypeScript SDK
Python SDK
AsyncOmni exposes the same operations with await. Python request arguments use
snake_case; response fields and nested expectedGroups members retain camelCase.
CLI --help and --schema include operation guidance generated from OpenAPI, so
terminal agents can discover these rules without a browser session.