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

# Create migration

> Queues a store-to-store migration and returns its `requestId`.

You reference stores by **BigCommerce store hash** — never by token. Vortex IQ resolves each store's API credentials server-side from your organisation's connected StagingPro integrations, so tokens never travel through the browser or your code.

Pass a `selection` to migrate a subset rather than the whole store.

**Guards enforced here**
- Source and destination must be different stores.
- `conflict_strategy: replace` ("Make Exact Copy") is refused when the destination is a live production store, because it deletes destination-only items.
- Selecting `orders`, `customers`, or `gift_certificates` requires `anonymise_customer_data: true`.
- Every `theme_activation` key must appear in `selection.themes`, and a destination channel may be targeted by at most one theme activation.
- Every `selection.page_template_globals` entry must appear in `selection.page_templates`.

<Note>Self-healing is always on. `auto_heal` is accepted for compatibility but the platform always runs migrations with auto-heal enabled — it and the shared learning layer materially reduce failed migrations, so it is not operator-optional.</Note>

The heart of a migration request is `selected_entities` — the list of entity groups to copy from the
source store to the destination. This page explains everything that list controls: what you can
migrate, how entities depend on one another, how bulk differs from selective, and what happens when
you pass something the engine doesn't recognise.

<Warning>
  **Unrecognised entity keys are silently ignored — they are not rejected.** The API validates only
  that `selected_entities` is a non-empty array of strings; it does **not** check the values against
  a known-entity list. A typo like `"product"` (instead of `"products"`) passes validation, migrates
  nothing, and returns no error. Use the exact keys from [Supported entities](#supported-entities).
</Warning>

## Bulk vs selective migration

A single request runs in one of two modes, decided entirely by whether you send a `selection`:

|                        | Bulk migration                                                                  | Selective migration                                                                                                                        |
| ---------------------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| **How you trigger it** | Send `selected_entities` with **no** `selection` (or an empty one).             | Send `selected_entities` **and** a `selection` with at least one non-empty id array.                                                       |
| **What migrates**      | Every item of each selected entity — the whole catalogue, all pages, and so on. | Only the ids you list, plus their required dependencies (see [Dependencies](#dependencies-and-automatic-includes)).                        |
| **Example**            | Migrate every product and category.                                             | Migrate products `101, 102` and theme `3f2b…` only.                                                                                        |
| **`replace` pruning**  | Destination-only items can be pruned.                                           | Pruning is **suppressed** — with only a subset of the source in hand, the engine cannot safely tell a genuine extra from an unpicked item. |
| **History label**      | Shown as **Bulk**.                                                              | Shown as **Selective**.                                                                                                                    |

A run counts as selective only when `selection` contains at least one non-empty array. An empty
`selection: {}` is treated as bulk.

<Note>
  The StagingPro UI's **Selective Migration** screen offers six pick tabs — Products, Categories,
  Pages, Promotions, Themes, and Page Templates. The request examples on this page include one
  example per tab (`Selective — Products tab`, and so on), each sending exactly the payload the UI
  sends: only that tab's entity keys, that tab's `selection` array, and `conflict_strategy: "update"`
  (selective runs update existing destination items — that is the UI's fixed behaviour). Selective
  picks also scope [entity counts](/vortex-apps/staging-pro/api/get-entity-counts) and the
  [forecast](/vortex-apps/staging-pro/api/get-forecast), so a ten-product pick is sized as a
  ten-product run.
</Note>

## Supported entities

Entities fall into families. Each **parent** entity has a key you place in `selected_entities`; most
parents also own **child entities** that carry the detail beneath them. Every child key is itself a
valid `selected_entities` value — selecting a child alone forces its parent to run so the id-mapping
exists (see [Dependencies](#dependencies-and-automatic-includes)).

<Warning>
  **Selecting a parent does not include its children — each child is opt-in.** `selected_entities:   ["products"]` migrates only the product **record** (name, price, SKU, description). It does **not**
  bring variants, images, options, modifiers, custom fields, metafields, bulk pricing, reviews, or
  channel assignments — you must list each of those keys explicitly. To migrate a complete catalogue,
  include the child keys (see the "Complete catalogue" request example above). The one exception:
  `custom_fields` travel automatically on a **selective** product run.
</Warning>

<AccordionGroup>
  <Accordion title="Catalog — products, brands, categories" icon="box">
    | Parent key   | Migrates   | Child entity keys                                                                                                                                                                                                                                              |
    | ------------ | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | `products`   | Products   | `images`, `videos`, `variants`, `modifiers`, `complex_rules`, `reviews`, `custom_fields`, `bulk_pricing`, `metafields`, `variant_metafields`, `related_products`, `channels` (channel assignments), `locale` (product locale), `product_template_associations` |
    | `brands`     | Brands     | `brand_metafields`, `brand_template_associations`                                                                                                                                                                                                              |
    | `categories` | Categories | `category_metafields`, `category_template_associations`                                                                                                                                                                                                        |

    * **`options` is not independently meaningful** — variant options migrate automatically as part
      of `variants`. There is no options-only migration, so to move them, select `variants`.
    * `variant_metafields` needs `variants`, and `complex_rules` needs `modifiers`.
    * When both `categories` and `products` are selected, product **sort order** ("featured" ordering
      within a category) migrates automatically — there is no separate key for it.
  </Accordion>

  <Accordion title="Content — pages, theme, email, blog, banners, scripts" icon="file-text">
    | Key                        | Migrates                                                                                                | Child entity keys                                                          |
    | -------------------------- | ------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
    | `pages`                    | Content pages                                                                                           | `page_widgets` (Page Builder widget content), `page_template_associations` |
    | `page_templates`           | The source **theme** (all Stencil page templates, uploaded and — for selective themes — activated)      | —                                                                          |
    | `email_templates`          | Transactional email templates                                                                           | —                                                                          |
    | `abandoned_cart_templates` | Abandoned-cart emails and settings                                                                      | —                                                                          |
    | `blog_posts`               | Blog posts                                                                                              | —                                                                          |
    | `banners`                  | Storefront banners                                                                                      | —                                                                          |
    | `scripts`                  | Script Manager scripts (API-visible only)                                                               | —                                                                          |
    | `content_assets`           | Mirrors content **images and links** referenced by pages, blog posts, banners, and Page Builder widgets | —                                                                          |
    | `page_widget_images`       | Mirrors Page Builder **widget images** only (a narrower `content_assets`)                               | —                                                                          |

    <Note>The theme key is `page_templates`, not `theme`. `theme` is accepted by the counts badge only and does **not** trigger a migration.</Note>

    <Note>
      `content_assets` and `page_widget_images` copy image files over **WebDAV**, so they only do
      something when (a) the destination store has WebDAV credentials configured, and (b) the run also
      migrates the content that references them (pages, blog posts, banners, or `page_widgets`).
      Otherwise they are skipped with a note.
    </Note>

    <Warning>
      **Two different "template" concepts — don't confuse them:**

      * The **`page_templates` entity** (in `selected_entities`) migrates the **theme**. For selective
        theme picks, pair it with `selection.themes` (uuids) and `theme_activation`.
      * The **`selection.page_templates` picks** (`pages/…` values) migrate **Page Builder
        template content** and are driven by the **`page_widgets`** entity, *not* `page_templates`.
        If you send `selection.page_templates` without `page_widgets` in `selected_entities`, the
        picks are silently ignored.
    </Warning>
  </Accordion>

  <Accordion title="Customers & pricing" icon="users">
    | Parent key          | Migrates                                          | Child entity keys                                                                                               |
    | ------------------- | ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
    | `customers`         | Customers (**PII always anonymised**)             | `customer_groups`, `customer_attributes`, `customer_addresses`, `customer_form_fields`, `customer_store_credit` |
    | `customer_segments` | Customer segments (marketing)                     | —                                                                                                               |
    | `price_lists`       | Price lists, their price records, and assignments | — (records + assignments always included)                                                                       |

    Customer addresses, form fields, and store credit migrate inline with the customer record rather
    than as separate progress rows. Unlike most child keys, they do **not** force the `customers`
    parent — you must select `customers` (or `customer_groups`/`customer_attributes`) as well, or they
    do nothing.
  </Accordion>

  <Accordion title="Marketing offers & orders" icon="tag">
    | Key                 | Migrates          | Notes                                                                              |
    | ------------------- | ----------------- | ---------------------------------------------------------------------------------- |
    | `promotions`        | v3 promotions     | Coupon codes migrate automatically for coupon-redemption promotions.               |
    | `coupons`           | Legacy v2 coupons | —                                                                                  |
    | `gift_certificates` | Gift certificates | **PII always anonymised.**                                                         |
    | `orders`            | Orders            | **PII always anonymised.** Filter by status with `order_status_{id}` keys (below). |

    To migrate only certain order statuses, add `order_status_{id}` keys alongside `orders` — for
    example `["orders", "order_status_11"]`. With no status keys, all statuses migrate.
  </Accordion>

  <Accordion title="Inventory" icon="warehouse">
    | Key                   | Migrates                                         | Notes                                            |
    | --------------------- | ------------------------------------------------ | ------------------------------------------------ |
    | `inventory_locations` | Inventory locations and their per-location stock | Requires the Inventory token scope on the store. |
  </Accordion>

  <Accordion title="Store settings" icon="settings">
    Grouped under **Store Settings** in progress. Each is a direct copy from source to destination;
    channel-scoped ones apply per storefront using your channel mapping.

    | Key                            | Migrates                                   |
    | ------------------------------ | ------------------------------------------ |
    | `store_profile`                | Store profile                              |
    | `store_locale`                 | Store locale                               |
    | `catalog_settings`             | Catalog settings                           |
    | `inventory_notifications`      | Inventory notifications                    |
    | `store_logo`                   | Store logo settings                        |
    | `email_statuses`               | Email statuses (per channel)               |
    | `channel_customer_settings`    | Channel customer settings (per channel)    |
    | `channel_order_settings`       | Channel order settings (per channel)       |
    | `web_analytics`                | Web analytics providers (per channel)      |
    | `currencies`                   | Currencies                                 |
    | `channel_currency_assignments` | Channel currency assignments (per channel) |
    | `shipping_zones`               | Shipping zones                             |
    | `shipping_methods`             | Shipping methods                           |
    | `tax_settings`                 | Tax settings, zones, and rates             |
  </Accordion>

  <Accordion title="Storefront settings" icon="monitor">
    Grouped under **Storefront Settings** in progress. Most apply per storefront (channel).

    | Key                         | Migrates                     |
    | --------------------------- | ---------------------------- |
    | `storefront_category`       | Storefront category settings |
    | `storefront_product`        | Storefront product settings  |
    | `storefront_search`         | Storefront search            |
    | `storefront_seo`            | Storefront SEO               |
    | `storefront_robots`         | Storefront robots.txt        |
    | `storefront_status`         | Storefront status            |
    | `storefront_security`       | Storefront security          |
    | `search_filters`            | Search filters               |
    | `search_contextual_filters` | Search contextual filters    |
    | `redirects`                 | 301 redirects                |
  </Accordion>
</AccordionGroup>

## Dependencies and automatic includes

Some entities can't stand alone — the engine pulls in what they need so references resolve on the
destination. Passing a wrong combination doesn't error; the engine adds the prerequisites or drops
the orphaned reference with a warning.

**Selecting a child forces its parent — but not the reverse.** Selecting `metafields` runs
`products`; `brand_metafields` runs `brands`; `category_metafields` runs `categories`; `page_widgets`
runs `pages`. The parent has to run so its source→destination id-map exists. The relationship is
one-way: selecting the **parent** (`products`) does *not* pull in its children — list each child key
you want (see the warning under [Supported entities](#supported-entities)).

**Selecting products auto-pulls the brands and categories they reference.** On a selective product
run, the engine reads each picked product's `brand_id` and category ids and migrates only those
brands and categories (plus category **ancestors**, so the tree is intact). A product's custom
fields also travel automatically on a selective product run, even if `custom_fields` isn't listed.

**Hierarchies auto-include ancestors.** Selective category picks pull in their parent categories;
selective page picks pull in their parent pages.

**Some children need a sibling.** `variant_metafields` needs `variants` (no variant id-map without
it); `complex_rules` needs `modifiers` (rules reference modifier values). If the sibling is absent,
that child is skipped with a note rather than failing the run.

**Cross-entity references are remapped or dropped.** Page widgets, promotions, and template
associations rewrite their entity ids through whichever id-maps the run built. A reference to
something not in the migration (for example a promotion targeting a category you didn't migrate) is
dropped with a warning, because it would 404 on the destination.

**Some storefront settings reference the catalogue.** Two settings carry catalogue ids and so
depend on those entities migrating in the same run:

* `search_contextual_filters` — BigCommerce contextual filters are configured **per category**
  ([Contextual Filters](https://developer.bigcommerce.com/docs/store-operations/catalog/contextual-filters)),
  so each context's `category_id` is remapped through the category id-map. It needs `categories`.
* `redirects` — 301 redirects that point at a product, category, brand, or page are remapped through
  those id-maps. It needs the entities its redirects target.

<Warning>
  **The API does not auto-add these prerequisites — the StagingPro UI does.** If you call the API
  with `search_contextual_filters` but omit `categories`, the category id-map is empty and **every
  category-scoped filter context is dropped with a warning** — the setting appears to migrate but the
  per-category overrides are silently lost. Always include `categories` alongside
  `search_contextual_filters` (and the relevant catalogue entities alongside `redirects`).
</Warning>

## Migration order

Entities always migrate in a fixed order so dependencies land first — you don't control it, and the
progress display's order is cosmetic. Each step mirrors a real reference in the BigCommerce API, so
an entity is never created before the ids it points at exist on the destination. The real order is:

<Steps>
  <Step title="Theme, then brands, then categories">
    The theme uploads first — BigCommerce themes are store-global and activated per storefront, so
    they have no catalogue dependency ([Multi-Storefront guide](https://developer.bigcommerce.com/docs/storefront/multi-storefront/guide)).
    Brands and categories go next because a product carries a `brand_id` and a `categories` array, so
    those ids must resolve first ([Create a Product](https://developer.bigcommerce.com/api-reference/store-management/catalog/products/createproduct)).
  </Step>

  <Step title="Pages, then products and their children">
    Product children — images, [variants](https://developer.bigcommerce.com/docs/rest-catalog/product-variants),
    [modifiers](https://developer.bigcommerce.com/docs/rest-catalog/product-modifiers), metafields,
    locale, and so on — are nested sub-resources under `/catalog/products/{id}`, so they migrate once
    the product id-map is built.
  </Step>

  <Step title="Page widgets, then store and storefront settings">
    Page Builder widgets run after the catalogue: a widget [placement](https://developer.bigcommerce.com/docs/rest-content/widgets/placement)
    takes an `entity_id` pointing at a specific product, brand, category, or page, so those ids must
    be mapped first. Store and storefront settings follow for the same reason — the ones that carry
    catalogue ids (per-category [contextual filters](https://developer.bigcommerce.com/docs/store-operations/catalog/contextual-filters),
    and 301 redirects that point at a product/category/brand/page) can only remap those ids once the
    catalogue has migrated.
  </Step>

  <Step title="Content, inventory, customers, pricing">
    Email, blog, banners, scripts, inventory locations, customer groups, customers, customer
    attributes, price lists. Customer groups come before customers and pricing because
    [price-list assignments](https://developer.bigcommerce.com/docs/rest-management/price-lists/price-lists-assignments)
    reference a `customer_group_id`.
  </Step>

  <Step title="Marketing offers, then orders">
    Customer segments and [promotions](https://developer.bigcommerce.com/docs/store-operations/promotions)
    run after the catalogue and customer groups, because promotion conditions reference categories,
    products, and customer `group_ids`. Orders run last: their line items reference `product_id` and
    the order carries a `customer_id` ([Order Products](https://developer.bigcommerce.com/docs/rest-management/orders/order-products)).
  </Step>
</Steps>

## Business rules that affect selection

<AccordionGroup>
  <Accordion title="Customer data must be anonymised" icon="user-lock">
    Selecting `orders`, `customers`, or `gift_certificates` requires `anonymise_customer_data: true`.
    Without it the request is rejected:

    ```json theme={null}
    { "error": "anonymise_required", "message": "orders/customers require anonymise_customer_data=true" }
    ```

    These entities carry personal data, so it is anonymised on the destination regardless.
  </Accordion>

  <Accordion title="Conflict strategy decides how existing items are handled" icon="git-merge">
    `conflict_strategy` controls what happens to an entity that already exists on the destination:

    * `skip` (**Add New Only**, default) — leave existing items untouched.
    * `update` (**Update Existing**) — additively update changed fields and child items.
    * `replace` (**Make Exact Copy**) — update, then delete destination-only items so the destination
      mirrors the source.

    `replace` is refused when the destination is a live production store, because it deletes data:

    ```json theme={null}
    { "error": "replace_not_allowed_on_production", "message": "'Make Exact Copy' can't be used here…" }
    ```

    On a **selective** run, `replace` still updates picked items but does not prune — a partial view
    of the source can't distinguish a genuine extra from an unpicked item.
  </Accordion>

  <Accordion title="Source and destination must differ" icon="ban">
    A store can never migrate onto itself — identical source and destination hashes are rejected. A
    store labelled **Production** can never be a destination.
  </Accordion>

  <Accordion title="Storefront scope (multi-storefront)" icon="store">
    With `source_channel_id` + `destination_channel_id`, the run maps one storefront to another. Set
    `channel_scoped_products: true` to migrate **only** the products assigned to that source
    storefront; otherwise all products migrate and are assigned to the mapped destination channel.
  </Accordion>

  <Accordion title="Selective theme activation" icon="palette">
    When `selection.themes` is present, the bulk `activate_theme` flag is ignored. Use
    `theme_activation` to activate a picked theme on specific destination channels. Two rules are
    enforced: every key must be a picked theme, and a destination channel may be activated by only
    one theme (a channel has exactly one active theme). `selection.page_template_globals` must be a
    subset of `selection.page_templates`.
  </Accordion>
</AccordionGroup>

## Invalid values and errors

| What you send                                                                                | What happens                                                                                                  |
| -------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| An unknown or mistyped entity key                                                            | **Accepted and silently ignored** — no error, nothing migrates for that key. Always use the exact keys above. |
| Empty `selected_entities`                                                                    | Rejected — the list must have at least one entry.                                                             |
| `orders` / `customers` / `gift_certificates` without `anonymise_customer_data: true`         | `400 anonymise_required`.                                                                                     |
| `replace` targeting a production destination                                                 | `422 replace_not_allowed_on_production`.                                                                      |
| A store hash not connected to your organisation                                              | `422 store_not_found`.                                                                                        |
| More ids than the cap (`products`/`categories` 5000, `pages`/`promotions` 2000, `themes` 20) | Validation error (`400` direct, `422` via the platform).                                                      |
| A non-UUID theme id, or a `page_templates` entry not starting `pages/`                       | Validation error.                                                                                             |
| `theme_activation` referencing an unpicked theme, or two themes on one channel               | `422` with an explanatory message.                                                                            |

<Tip>
  Because unknown keys are accepted without complaint, the safest way to build `selected_entities` is
  to copy keys directly from [Supported entities](#supported-entities) above rather than typing them
  from memory.
</Tip>


## OpenAPI

````yaml vortex-apps/staging-pro/api/openapi.json POST /migrations
openapi: 3.1.0
info:
  title: StagingPro V2 Migration API (BigCommerce)
  version: 2.0.0
  description: >-
    REST API for the StagingPro V2 BC Migration app. Browse a connected store's
    catalogue, size a migration before you run it, launch bulk or selective
    store-to-store migrations, control runs, and follow live progress.


    All endpoints are served from `https://app.vortexiq.ai/v2/api/bc-migration`
    and are scoped to your organisation. Authenticate with a bearer token from
    the Login endpoint (scripts and services) or your signed-in session
    (browser). See
    [Authentication](/vortex-apps/staging-pro/api/authentication).
  contact:
    name: Vortex IQ Support
    url: https://www.vortexiq.ai/contact-us
servers:
  - url: https://app.vortexiq.ai/v2/api/bc-migration
    description: Vortex IQ platform (production)
security:
  - bearerAuth: []
tags:
  - name: Authentication
    description: >-
      Exchange your identity and your organisation's API credential for a bearer
      token.
  - name: Migrations
    description: >-
      Create migrations, list history, read run detail and per-item issues, and
      follow live progress.
  - name: Migration Controls
    description: Pause, resume, cancel, and verify-only re-runs.
  - name: Counts & Forecast
    description: Size a migration before launching it. Neither endpoint creates a run.
paths:
  /migrations:
    post:
      tags:
        - Migrations
      summary: Create migration
      description: >-
        Queues a store-to-store migration and returns its `requestId`.


        You reference stores by **BigCommerce store hash** — never by token.
        Vortex IQ resolves each store's API credentials server-side from your
        organisation's connected StagingPro integrations, so tokens never travel
        through the browser or your code.


        Pass a `selection` to migrate a subset rather than the whole store.


        **Guards enforced here**

        - Source and destination must be different stores.

        - `conflict_strategy: replace` ("Make Exact Copy") is refused when the
        destination is a live production store, because it deletes
        destination-only items.

        - Selecting `orders`, `customers`, or `gift_certificates` requires
        `anonymise_customer_data: true`.

        - Every `theme_activation` key must appear in `selection.themes`, and a
        destination channel may be targeted by at most one theme activation.

        - Every `selection.page_template_globals` entry must appear in
        `selection.page_templates`.


        <Note>Self-healing is always on. `auto_heal` is accepted for
        compatibility but the platform always runs migrations with auto-heal
        enabled — it and the shared learning layer materially reduce failed
        migrations, so it is not operator-optional.</Note>
      operationId: createMigration
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateMigrationRequest'
            examples:
              fullMigration:
                summary: Full migration — every entity, in migration order
                description: >-
                  A complete store copy. Paste it, replace the token and the two
                  store hashes, and remove any keys you don't want. Entities are
                  listed in the order they migrate (theme → catalogue → settings
                  → content → customers → pricing → offers → orders → image
                  mirror). `anonymise_customer_data` is `true` because the list
                  includes orders, customers, and gift certificates — those
                  require it. To limit orders to certain statuses, add
                  `order_status_{id}` keys. `content_assets` (image mirroring)
                  only runs if the destination has WebDAV credentials
                  configured.
                value:
                  source_store_hash: abc123xyz
                  destination_store_hash: def456uvw
                  mode: full
                  conflict_strategy: skip
                  anonymise_customer_data: true
                  selected_entities:
                    - page_templates
                    - brands
                    - brand_metafields
                    - brand_template_associations
                    - categories
                    - category_metafields
                    - category_template_associations
                    - pages
                    - page_template_associations
                    - products
                    - images
                    - videos
                    - variants
                    - modifiers
                    - complex_rules
                    - reviews
                    - custom_fields
                    - bulk_pricing
                    - metafields
                    - variant_metafields
                    - related_products
                    - channels
                    - locale
                    - product_template_associations
                    - page_widgets
                    - store_profile
                    - store_locale
                    - catalog_settings
                    - inventory_notifications
                    - store_logo
                    - email_statuses
                    - channel_customer_settings
                    - channel_order_settings
                    - web_analytics
                    - currencies
                    - channel_currency_assignments
                    - shipping_zones
                    - shipping_methods
                    - tax_settings
                    - storefront_category
                    - storefront_product
                    - storefront_search
                    - storefront_seo
                    - storefront_robots
                    - storefront_status
                    - storefront_security
                    - search_filters
                    - search_contextual_filters
                    - redirects
                    - email_templates
                    - abandoned_cart_templates
                    - blog_posts
                    - banners
                    - scripts
                    - inventory_locations
                    - customer_groups
                    - customers
                    - customer_addresses
                    - customer_form_fields
                    - customer_store_credit
                    - customer_attributes
                    - price_lists
                    - customer_segments
                    - promotions
                    - coupons
                    - gift_certificates
                    - orders
                    - content_assets
              parentsOnly:
                summary: Parent records only (no children)
                description: >-
                  Migrates just the product, category, and brand records — no
                  variants, images, custom fields, metafields, and so on. Useful
                  for a quick structural copy; rarely what you want for a real
                  catalogue move.
                value:
                  source_store_hash: abc123xyz
                  destination_store_hash: def456uvw
                  selected_entities:
                    - products
                    - categories
                    - brands
                  mode: full
                  conflict_strategy: skip
              selectiveProducts:
                summary: Selective — Products tab
                description: >-
                  What the UI's Products tab sends. A product pick brings its
                  full detail plus the brands and categories it references
                  (dependencies come along automatically), and existing
                  destination items are updated. Add `locale` when the
                  destination is multi-storefront.
                value:
                  source_store_hash: abc123xyz
                  destination_store_hash: def456uvw
                  selected_entities:
                    - products
                    - brands
                    - categories
                    - variants
                    - variant_metafields
                    - custom_fields
                    - bulk_pricing
                    - images
                    - videos
                    - metafields
                    - reviews
                    - related_products
                    - channels
                    - modifiers
                    - complex_rules
                    - product_template_associations
                    - brand_template_associations
                    - category_template_associations
                  mode: full
                  conflict_strategy: update
                  selection:
                    products:
                      - 101
                      - 102
                      - 103
                  source_channel_id: 1
                  destination_channel_id: 1
              selectiveCategories:
                summary: Selective — Categories tab
                description: >-
                  Picked categories (ancestors are auto-included so the tree
                  stays intact) with their metafields and template associations.
                value:
                  source_store_hash: abc123xyz
                  destination_store_hash: def456uvw
                  selected_entities:
                    - categories
                    - category_metafields
                    - category_template_associations
                  mode: full
                  conflict_strategy: update
                  selection:
                    categories:
                      - 21
                      - 22
                  source_channel_id: 1
                  destination_channel_id: 1
              selectivePages:
                summary: Selective — Pages tab
                description: >-
                  Picked pages (parent pages auto-included) with their Page
                  Builder widget content and template associations.
                value:
                  source_store_hash: abc123xyz
                  destination_store_hash: def456uvw
                  selected_entities:
                    - pages
                    - page_template_associations
                    - page_widgets
                  mode: full
                  conflict_strategy: update
                  selection:
                    pages:
                      - 7
                      - 8
                  source_channel_id: 1
                  destination_channel_id: 1
              selectivePromotions:
                summary: Selective — Promotions tab
                description: >-
                  Picked promotions. `customer_groups` rides along because
                  promotion targeting references group ids.
                value:
                  source_store_hash: abc123xyz
                  destination_store_hash: def456uvw
                  selected_entities:
                    - promotions
                    - customer_groups
                  mode: full
                  conflict_strategy: update
                  selection:
                    promotions:
                      - 31
                  source_channel_id: 1
                  destination_channel_id: 1
              selectiveThemes:
                summary: Selective — Themes tab
                description: >-
                  Picked themes by uuid (the Theme entity key is
                  `page_templates`). `theme_activation` optionally activates
                  each migrated theme on destination channels; omit it to upload
                  without activating. A themes-only run may omit the channel
                  keys entirely.
                value:
                  source_store_hash: abc123xyz
                  destination_store_hash: def456uvw
                  selected_entities:
                    - page_templates
                  mode: full
                  conflict_strategy: update
                  selection:
                    themes:
                      - 3f2b8c14-9d5e-4a71-b0c6-2e8f7a1d4b93
                  theme_activation:
                    3f2b8c14-9d5e-4a71-b0c6-2e8f7a1d4b93:
                      - 1
              selectivePageTemplates:
                summary: Selective — Page Templates tab
                description: >-
                  Picked template files' Page Builder content — driven by the
                  `page_widgets` entity. `page_template_globals` marks the picks
                  whose channel-wide global widget content also migrates (must
                  be a subset of `page_templates`).
                value:
                  source_store_hash: abc123xyz
                  destination_store_hash: def456uvw
                  selected_entities:
                    - page_widgets
                  mode: full
                  conflict_strategy: update
                  selection:
                    page_templates:
                      - pages/home
                      - pages/category
                    page_template_globals:
                      - pages/home
                  source_channel_id: 1
                  destination_channel_id: 1
              singleStorefront:
                summary: One storefront to another (MSF)
                value:
                  source_store_hash: abc123xyz
                  destination_store_hash: def456uvw
                  selected_entities:
                    - products
                    - categories
                  source_channel_id: 1
                  destination_channel_id: 2
                  channel_scoped_products: true
                  source_storefront: UK Storefront
                  destination_storefront: UK Staging
              dryRun:
                summary: Dry run (no writes)
                value:
                  source_store_hash: abc123xyz
                  destination_store_hash: def456uvw
                  selected_entities:
                    - products
                  mode: dry-run
      responses:
        '201':
          description: Migration queued.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateMigrationResponse'
              example:
                requestId: 5A3C1B2E-7F4D-4E6A-9B8C-0D1E2F3A4B5C
                status: queued
                mode: full
                forecast:
                  products: 1200
                  variants: 5400
                  brands: 40
                  categories: 210
                  memMB: 2048
                  cpus: 2
                  etaSeconds: 5400
                  tier: medium
                  basis: forecast
                  at: '2026-07-16T09:00:00.000Z'
        '400':
          description: >-
            The migration service rejected the request (for example
            `anonymise_required`, or source and destination are the same store).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          description: >-
            Validation failed, a store is not found or not authorised for your
            organisation (`store_not_found`), or `replace` was requested against
            a production destination (`replace_not_allowed_on_production`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error: replace_not_allowed_on_production
                message: >-
                  'Make Exact Copy' can't be used here: the destination you
                  selected is a live production store, and this option deletes
                  anything that exists only on the destination. Please choose
                  'Add New Only' or 'Update Existing', or pick a staging
                  (non-production) destination store.
        '502':
          $ref: '#/components/responses/ProxyFailed'
components:
  schemas:
    CreateMigrationRequest:
      type: object
      required:
        - source_store_hash
        - destination_store_hash
        - selected_entities
      properties:
        source_store_hash:
          type: string
          description: >-
            BigCommerce store hash to migrate **from**. Must be connected to
            your organisation.
        destination_store_hash:
          type: string
          description: >-
            BigCommerce store hash to migrate **to**. Must differ from the
            source.
        selected_entities:
          type: array
          items:
            type: string
          minItems: 1
          description: >-
            Which entity groups to migrate — for example `products`,
            `categories`, `brands`, `pages`, `page_templates`, `promotions`,
            `orders`, `customers`. Child keys (e.g. `variants`, `metafields`,
            `page_widgets`) and settings keys are also valid and force their
            parent to run. **Unknown keys are silently ignored, not rejected.**
            See the full catalogue, dependencies, and rules above.
        mode:
          type: string
          enum:
            - full
            - dry-run
            - verify
            - counts
          default: full
          description: >-
            `full` migrates; `dry-run` walks the migration without writing;
            `verify` compares only; `counts` totals only.
        anonymise_customer_data:
          type: boolean
          default: false
          description: >-
            Required `true` when `orders`, `customers`, or `gift_certificates`
            are selected.
        conflict_strategy:
          type: string
          enum:
            - skip
            - update
            - replace
          description: >-
            How an entity that already exists on the destination is handled:

            - `skip` — Add New Only (default): leave existing items untouched.

            - `update` — Update Existing: additively update changed fields.

            - `replace` — Make Exact Copy: update, then delete destination-only
            items. **Refused against a production destination.**
        activate_theme:
          type: boolean
          description: >-
            Bulk theme migration: activate the migrated theme on the destination
            rather than only uploading it. Ignored when `selection.themes` is
            present — use `theme_activation` instead.
        auto_heal:
          type: boolean
          description: Accepted for compatibility only. Self-healing is always enabled.
        source_channel_id:
          type: integer
          minimum: 1
          description: >-
            Multi-storefront: the source storefront to migrate from. Pair with
            `destination_channel_id`.
        destination_channel_id:
          type: integer
          minimum: 1
          description: 'Multi-storefront: the destination storefront to map onto.'
        channel_scoped_products:
          type: boolean
          description: >-
            Migrate only products assigned to `source_channel_id`. When false,
            all products migrate and are assigned to the mapped destination
            channel.
        channel_map:
          type: object
          additionalProperties:
            type: integer
          description: >-
            Explicit source channel id → destination channel id map, for mapping
            several storefronts at once.
        source_storefront:
          type: string
          description: Source storefront name, recorded for display in History.
        destination_storefront:
          type: string
          description: Destination storefront name, recorded for display in History.
        selection:
          $ref: '#/components/schemas/Selection'
        theme_activation:
          $ref: '#/components/schemas/ThemeActivation'
    CreateMigrationResponse:
      type: object
      properties:
        requestId:
          type: string
          description: Identifies the run. Use it with every other migration endpoint.
        status:
          type: string
          enum:
            - queued
        mode:
          type: string
          enum:
            - full
            - dry-run
            - verify
            - counts
        forecast:
          oneOf:
            - $ref: '#/components/schemas/Forecast'
            - type: 'null'
          description: Sizing forecast computed at kickoff, when available.
    Error:
      type: object
      properties:
        error:
          type: string
          description: Machine-readable error code.
        message:
          type: string
          description: Human-readable explanation.
        status:
          type: integer
          description: 'Present on `bc_error`: the status BigCommerce returned.'
        errors:
          type: object
          additionalProperties: true
          description: 'Present on validation failures: field-level messages.'
    Selection:
      type: object
      description: >-
        Selective migration picks. Parents and ancestors are included
        automatically, and products pull in their brands and categories.


        Find the ids to pass here in the StagingPro app's Selective Migration
        tab. A selection also scopes [entity
        counts](/vortex-apps/staging-pro/api/get-entity-counts) and the
        [forecast](/vortex-apps/staging-pro/api/get-forecast).
      properties:
        products:
          type: array
          items:
            type: integer
          maxItems: 5000
          description: Product ids.
        categories:
          type: array
          items:
            type: integer
          maxItems: 5000
          description: Category ids.
        pages:
          type: array
          items:
            type: integer
          maxItems: 2000
          description: Page ids.
        promotions:
          type: array
          items:
            type: integer
          maxItems: 2000
          description: Promotion ids.
        themes:
          type: array
          items:
            type: string
            format: uuid
          maxItems: 20
          description: >-
            Theme **UUIDs** (BigCommerce theme ids are uuids, not integers).
            Requires `page_templates` in `selected_entities` (that is the Theme
            entity) — themes migrate only when the Theme phase runs. When
            present, `activate_theme` is ignored in favour of
            `theme_activation`.
        page_templates:
          type: array
          items:
            type: string
            pattern: ^pages/
          maxItems: 50
          description: >-
            Page-Builder template files, all beginning `pages/` (for example
            `pages/home`). Template-level content only — content scoped to a
            specific product, category, or page is not included. Requires
            **`page_widgets`** in `selected_entities` (not the `page_templates`
            entity, which is the Theme). Without `page_widgets` these picks are
            ignored.
        page_template_globals:
          type: array
          items:
            type: string
            pattern: ^pages/
          maxItems: 50
          description: >-
            Page templates whose **global** widget content also migrates. Must
            be a subset of `page_templates`.
    ThemeActivation:
      type: object
      additionalProperties:
        type: array
        items:
          type: integer
          minimum: 1
        minItems: 1
      description: >-
        Selective themes only: which destination channels to **activate** each
        picked theme on, keyed by source theme uuid. Omit to upload the themes
        without activating them.


        Every key must appear in `selection.themes`, and a destination channel
        may be targeted by at most one theme — a storefront channel has exactly
        one active theme.
      example:
        3f2b8c14-9d5e-4a71-b0c6-2e8f7a1d4b93:
          - 1
          - 2
    Forecast:
      type: object
      description: >-
        Sizing and ETA estimate.


        Sizing and ETA are priced from different bases deliberately: the ETA
        follows the run's real scope (picks, strategy, order statuses), while
        memory is pinned to **store scale** — the engine's working set does not
        shrink with a small pick, so sizing on picked counts alone would
        under-provision the run.
      properties:
        products:
          type: integer
        variants:
          type: integer
        brands:
          type: integer
        categories:
          type: integer
        counts:
          type: object
          additionalProperties:
            type: integer
          description: >-
            Per-entity counts the estimate was priced from, scoped by
            `selection` when supplied.
        estimated:
          type: array
          items:
            type: string
          description: Count keys that are sampled estimates rather than exact totals.
        memMB:
          type: integer
          description: >-
            Memory the run will be given (MiB). Pinned to store scale, not to
            the picked subset.
        cpus:
          type: number
          description: CPUs the run will be given.
        etaSeconds:
          type: integer
          description: Estimated total wall-clock for the run.
        etaBreakdown:
          type: object
          additionalProperties:
            type: number
          description: Per-phase seconds.
        etaAssumedRttMs:
          type: number
          description: The blended latency the estimate assumed.
        etaAssumedMs:
          type: object
          description: >-
            Per-operation-class latencies the estimate assumed, so heavy writes
            are not judged against a blended baseline.
          properties:
            read:
              type: number
            write:
              type: number
            heavy:
              type: number
        strategy:
          type: string
          enum:
            - skip
            - update
            - replace
          description: >-
            The conflict strategy the ETA was priced with. Absent means
            create-only.
        destProducts:
          type: integer
          description: >-
            Destination catalogue size at kickoff. Absent or `0` when the
            destination wasn't known.
        tier:
          type: string
          enum:
            - small
            - medium
            - large
            - xlarge
            - xxlarge
            - xxxlarge
          description: Capacity band the run was placed in.
        basis:
          type: string
          enum:
            - forecast
            - fallback
            - fix
          description: '`fallback` means counts were unavailable and defaults were used.'
        at:
          type: string
          format: date-time
  responses:
    Forbidden:
      description: >-
        Store Migration is not enabled for your organisation. Requests with no
        valid bearer token or session return `401` instead.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            message: Store Migration is not enabled for this organization.
    ProxyFailed:
      description: The migration service is unreachable (`proxy_failed`).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: proxy_failed
            message: migration service unreachable
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: >-
        A bearer token from [Login](/vortex-apps/staging-pro/api/login), sent as
        `Authorization: Bearer <token>`. Valid for 10 days; scoped to the user
        and organisation it was issued for. Rotating or disabling the
        organisation's API credential immediately invalidates all outstanding
        tokens. Browser calls from a signed-in app.vortexiq.ai page are also
        accepted via session cookie — see
        [Authentication](/vortex-apps/staging-pro/api/authentication).

````