> ## Documentation Index
> Fetch the complete documentation index at: https://docs.omnicommerce.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Merge Duplicate Products

> Merge exact-SKU duplicate products into one master product per group. The master keeps its content, images, price and stock; the group's other products are archived into it, and orders, reviews, promotions and other catalog references move to the master.

All marketplace listings move to the master, including listings in other stores of the same marketplace. Different listing identities in the same store block the group. No marketplace listing is delisted, rewritten or republished.

Each group commits independently; the response lists every group's outcome. Sources with inventory history or immutable order composition snapshots must be reconciled before archiving so returns and stock replays keep their targets.

Supports every OmniCommerce marketplace. Variant families, bundles and products in different currencies cannot be merged. A stock-connected product may be kept as master; it cannot be archived into another product. Use preview masterSelections to choose an allowed master.

### Authorization

Requires `catalog:write`. This is a catalog operation with no remote marketplace writes.

### Limits

2–20 products and up to 10 groups per request. Pin a reviewed preview with `expectedGroups` to refuse groups that changed since.

Set dryRun=true to simulate the same transaction and roll it back. The response has data.dryRun=true; succeededCount/failedCount count validation outcomes, mergedProductIds is empty and wouldMergeProductIds lists the planned sources. No product update events or duplicate scan refreshes are emitted. A successful simulation is not a saved merge; real execution validates current data again.



## OpenAPI

````yaml /openapi.json post /api/v1/products/duplicates/merge
openapi: 3.1.0
info:
  title: OmniCommerce API
  version: 1.0.0
  license:
    name: Proprietary
    url: https://omnicommerce.sg/terms
  description: >-
    OmniCommerce API surface for agents and developer integrations. Includes
    public endpoints (merchant discovery, product search, compare, catalog, ACO
    retrieval) and authenticated endpoints (product management via API key
    Bearer tokens).
servers:
  - url: https://omnicommerce.ai
    description: Production
  - url: http://localhost:3000
    description: Local development
security: []
tags:
  - name: agentic-checkout
    description: Merchant-fulfilled agentic checkout
    x-group: Agentic Checkout
  - name: agents
    description: agents
    x-group: Agents
  - name: analytics
    description: Sales performance across marketplaces
    x-group: Analytics
  - name: aops
    description: Agent operating procedures
    x-group: AOPs
  - name: branding
    description: branding
    x-group: Branding
  - name: campaigns
    description: Campaign briefs, offer windows, and marketplace runs
    x-group: Campaigns
  - name: catalog
    description: catalog
    x-group: Catalog
  - name: commerce
    description: commerce
    x-group: Commerce
  - name: developer-platform
    description: Authenticated developer platform APIs
    x-group: Developer Platform
  - name: documents
    description: Knowledge base document uploads
    x-group: Knowledge Base
  - name: inventory
    description: Inventory quantities, planning signals, and marketplace listing absence
    x-group: Inventory
  - name: looks
    description: AI-styled product looks
    x-group: Looks
  - name: oauth
    description: OAuth token endpoints
    x-group: OAuth
  - name: orders
    description: Workspace and agentic orders
    x-group: Orders
  - name: organizations
    description: Organization management
    x-group: Organizations
  - name: price-books
    description: Marketplace list-price markup rules
    x-group: Price Books
  - name: products
    description: Product CRUD, bulk import, enrichment, publish, and delist
    x-group: Products
  - name: promotions
    description: Central promotions and marketplace sync
    x-group: Promotions
  - name: public-agent
    description: Public agent discovery and catalog APIs
    x-group: Public Agent
  - name: returns
    description: returns
    x-group: Returns
  - name: reviews
    description: Marketplace product reviews and seller replies
    x-group: Reviews
  - name: scripts
    description: scripts
    x-group: Scripts
  - name: settlements
    description: settlements
    x-group: Settlements
  - name: ucp
    description: Universal Commerce Protocol
    x-group: UCP
paths:
  /api/v1/products/duplicates/merge:
    post:
      tags:
        - products
      summary: Merge Duplicate Products
      description: >-
        Merge exact-SKU duplicate products into one master product per group.
        The master keeps its content, images, price and stock; the group's other
        products are archived into it, and orders, reviews, promotions and other
        catalog references move to the master.


        All marketplace listings move to the master, including listings in other
        stores of the same marketplace. Different listing identities in the same
        store block the group. No marketplace listing is delisted, rewritten or
        republished.


        Each group commits independently; the response lists every group's
        outcome. Sources with inventory history or immutable order composition
        snapshots must be reconciled before archiving so returns and stock
        replays keep their targets.


        Supports every OmniCommerce marketplace. Variant families, bundles and
        products in different currencies cannot be merged. A stock-connected
        product may be kept as master; it cannot be archived into another
        product. Use preview masterSelections to choose an allowed master.


        ### Authorization


        Requires `catalog:write`. This is a catalog operation with no remote
        marketplace writes.


        ### Limits


        2–20 products and up to 10 groups per request. Pin a reviewed preview
        with `expectedGroups` to refuse groups that changed since.


        Set dryRun=true to simulate the same transaction and roll it back. The
        response has data.dryRun=true; succeededCount/failedCount count
        validation outcomes, mergedProductIds is empty and wouldMergeProductIds
        lists the planned sources. No product update events or duplicate scan
        refreshes are emitted. A successful simulation is not a saved merge;
        real execution validates current data again.
      operationId: post_v1_products_duplicates_merge
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required:
                - productIds
              properties:
                dryRun:
                  type: boolean
                  description: >-
                    True runs the same merge transaction and rolls it back,
                    including database reference/constraint validation. No
                    product changes are saved, no mutation events or duplicate
                    refreshes run, and marketplace listings stay unchanged.
                    Requires catalog:write. Omitted/false performs the reviewed
                    merge. This differs from CLI --dry-run, which only prints a
                    request.
                organizationId:
                  type: string
                  description: >-
                    Required only for session-authenticated calls. API
                    credentials use their own organization.
                productIds:
                  type: array
                  minItems: 2
                  maxItems: 20
                  items:
                    type: string
                    minLength: 1
                    maxLength: 255
                  description: >-
                    Ordered standalone catalog product IDs. Products are grouped
                    by exact SKU; each group merges into one master.
                masterProductIds:
                  type: array
                  minItems: 1
                  maxItems: 10
                  items:
                    type: string
                    minLength: 1
                    maxLength: 255
                  description: >-
                    Optional master per SKU group, chosen from productIds; at
                    most one per group. Defaults to the first requested product
                    of each group. The master keeps its content, images, price
                    and stock.
                expectedGroups:
                  type: array
                  minItems: 1
                  maxItems: 10
                  description: >-
                    Optional groups from a preview to pin: membership, master
                    (first product) and SKU. Groups that changed or are omitted
                    are not merged.
                  items:
                    type: object
                    additionalProperties: false
                    required:
                      - productIds
                      - sku
                    properties:
                      productIds:
                        type: array
                        minItems: 2
                        maxItems: 20
                        items:
                          type: string
                          minLength: 1
                          maxLength: 255
                      sku:
                        type: string
                        minLength: 1
                        maxLength: 255
                explanation:
                  type: string
                  maxLength: 2000
            example:
              productIds:
                - 4d3c2b1a-0000-4000-8000-000000000001
                - 4d3c2b1a-0000-4000-8000-000000000002
              masterProductIds:
                - 4d3c2b1a-0000-4000-8000-000000000002
      responses:
        '200':
          description: >-
            Batch processed. `data` holds `groups[]` (with `survivingProductId`,
            `mergedProductIds`, `error`), ordered per-product `items[]`,
            `succeededCount` and `failedCount`. `ok` is true only when every
            group merged.
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok:
                    type: boolean
                  apiVersion:
                    type: string
                  operationId:
                    type: string
                  status:
                    type: string
                    enum:
                      - accepted
                      - completed
                      - failed
                  data:
                    type: object
                    properties:
                      success:
                        type: boolean
                      dryRun:
                        type: boolean
                      productIds:
                        type: array
                        items:
                          type: string
                          minLength: 1
                      survivingProductId:
                        type: string
                        minLength: 1
                      survivingProductIds:
                        type: array
                        items:
                          type: string
                          minLength: 1
                      mergedProductIds:
                        type: array
                        items:
                          type: string
                          minLength: 1
                      wouldMergeProductIds:
                        type: array
                        items:
                          type: string
                          minLength: 1
                      groups:
                        type: array
                        items:
                          type: object
                          properties:
                            productIds:
                              type: array
                              items:
                                type: string
                                minLength: 1
                            success:
                              type: boolean
                            survivingProductId:
                              type: string
                              minLength: 1
                            mergedProductIds:
                              type: array
                              items:
                                type: string
                                minLength: 1
                            wouldMergeProductIds:
                              type: array
                              items:
                                type: string
                                minLength: 1
                            error:
                              type: string
                              minLength: 1
                          required:
                            - productIds
                            - success
                          additionalProperties: false
                      succeededCount:
                        type: integer
                        minimum: 0
                        maximum: 9007199254740991
                      failedCount:
                        type: integer
                        minimum: 0
                        maximum: 9007199254740991
                      items:
                        type: array
                        items:
                          type: object
                          properties:
                            productId:
                              type: string
                              minLength: 1
                            success:
                              type: boolean
                            survivingProductId:
                              type: string
                              minLength: 1
                            disposition:
                              type: string
                              enum:
                                - survivor
                                - merged
                            error:
                              type: string
                              minLength: 1
                          required:
                            - productId
                            - success
                          additionalProperties: false
                      message:
                        type: string
                        minLength: 1
                      error:
                        type: string
                        minLength: 1
                      explanation:
                        type: string
                        minLength: 1
                    required:
                      - success
                      - productIds
                      - succeededCount
                      - failedCount
                      - items
                    additionalProperties: false
                  error:
                    type: string
                  warnings:
                    type: array
                    items:
                      type: string
                  recommendations:
                    type: array
                    items:
                      type: string
                  links:
                    type: object
                    propertyNames:
                      type: string
                    additionalProperties:
                      type: string
                required:
                  - ok
                  - apiVersion
                  - operationId
                  - status
                  - data
                  - warnings
                  - recommendations
                  - links
                additionalProperties: false
        '400':
          description: Invalid payload.
        '401':
          description: Unauthorized
        '403':
          description: Credential lacks catalog:write.
        '500':
          description: Internal Server Error
      security:
        - bearerAuth: []
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: API Key
      description: >-
        Bearer API key for server-to-server access. Session auth is also
        supported in first-party UI flows.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.