Skip to main content
Once you start publishing, MerchantOps tracks where each product and price is live, whether the live data still matches what’s in MerchantOps, who published it, and what is scheduled next. This page explains the status indicators so you can tell at a glance what’s published, what’s drifted, what failed, and what’s coming.

Target pills

A product can be published to more than one target and environment, for example a QA VTEX store and a production VTEX store, or a VTEX store and a Shopify store. MerchantOps shows one pill per enabled target, so you see each destination on its own: one product can be live on production and modified on QA at the same time. The same pill appears everywhere publish status is shown: the product list, product detail, the catalog and pricing batch pages, and price detail. Each pill is in one of these states: If more than one applies, the state higher in the table wins. A target that is live, modified, or failed and also has a publish scheduled keeps its own state, and the hover shows the next scheduled publish.

What the hover shows

Hover a pill to see the facts behind it:
  • the target name and environment, with a one-line description of the state;
  • the version published (for a price: the SKU, price type, amount, and effective date);
  • when it was published;
  • who published it. A publish that ran automatically on its effective date reads scheduled · approved by the member who approved the batch;
  • the batch that published it and the job that ran it, each linking to its page;
  • the next scheduled publish, or, on an older product version, which version is live on that target now.
Publish times are shown in your browser’s time zone, with the zone abbreviation. Effective dates are dates in your organization’s publishing time zone, and are labelled “org time” when that differs from your browser’s.
Publishes made before MerchantOps recorded the batch, job, and member on every publish may not have all three. Their pills still show the correct state; the hover just leaves out the details, or the links, it cannot resolve.

The summary status still exists

The product list used to show a summary pill (Draft, Published, or Modified) next to the per-target pills. It has been removed; the per-target pills say the same thing more precisely. The summary is still computed, though:
  • The product list’s All Publish Status filter still filters on Draft (never published anywhere), Published (in sync everywhere it is published), and Modified (edited since it was last pushed to at least one target).
  • The API still returns it as publish_status, with the values draft, live, and changed.
Variant-only edits do not show up here. Drift is detected by comparing a fingerprint of the product’s product-level values against what was last published; that fingerprint does not yet include variants. Change a variant’s size, color, or barcode and the pill stays live; neither the pills nor the summary status catch it. Republish deliberately after editing variants.
An empty value counts the same as a missing one. A field that is blank, an empty list, or simply not set is treated alike, so clearing a field that was empty when the product was published leaves the pill live. Clearing a field that held a value is a real change and turns the pill modified.
A batch approved without a publishing target is shown as active in MerchantOps but is never published, so it produces no publish status. Its products keep never published pills (and a Draft summary) until a batch publishes them to a target.
Older Shopify publishes may read modified. Shopify publishes made before MerchantOps began recording a content fingerprint for them lack what the comparison needs, so a product published to Shopify then can show modified ● even though nothing changed. Republishing the product to that Shopify store records the fingerprint and turns the pill green.

Where you see the pills

The Status column shows one pill per enabled catalog target. The column header’s legend explains each state.
  • Header. On the latest version, the pills are exactly the ones in the product list, so the two never disagree. Enrichment adds versions often, so the latest version may be #12 while a target holds #10 with identical content. The #12 pill is then live, and its hover says the target holds #10 with identical content. On an older version, each target shows live if that version is the one live there now, superseded ✓ if it was published there and later replaced, failed ✕ if its latest attempt there failed, and otherwise scheduled or never published.
  • Version cards. Each version card shows a compact pill (for example PROD or QA) for every target that version was published to or attempted on. See Versioning.
  • Publishing history. A collapsible section listing every publish of the product, failures included, with version, target, status, time, who, batch, and job.
  • A Prices ↗ link opens the product’s price detail page.
The records table’s Publish column shows a pill for each record against the batch’s target. A record in an approved batch whose effective date is still ahead shows scheduled. See Publishing overview.
The Price Records table has a Publish column. SKU rows show one pill for the batch’s target; style and color rows roll their SKUs up (for example “VTEX QA · 1/2”), reading as a warning when only some published and as failed when none did. See Price batches.
An All variants · last publish per target row summarizes the product’s prices with one pill per enabled pricing target, including hollow never published pills. Variant and record rows show a pill for every enabled target too. See Price records.

From the API, MCP, or CLI

Agents and integrations can read the same facts.
product:read
The product’s publish history plus its current per-target status.
  • history lists one row per batch, target, and environment, newest first. A version published to the same target by two batches has two rows; republishing the same batch replaces that batch’s row. Each row carries version, the target, status (success or failed), attempted_at, attribution (batch, job, who, and how it was triggered), and error on failures. published_at is the attempt time, failed rows included. version is null for an older record whose version can’t be determined.
  • Failed attempts are left out unless you pass include_failed=true. The default returns successes only, as before.
  • history is capped at 200 rows; truncated is true when more exist.
  • current holds the summary publish_status and one chip per enabled target. live maps each target to the version live there now, scheduled lists upcoming publishes, and targets lists every enabled catalog target, including ones never published to.
product:read
The product list and search endpoints and GET /api/products/{key} return one chip per enabled catalog target in publish_chips. The single-product GET /api/products/{key} used to return an empty list here; it now matches the list. Each chip keeps its state of in_sync, drifted, or never_published, and adds attribution, last_attempt (the newest attempt, including failures), next_scheduled, and in_flight (a publish running now). The pill states above are derived from these fields.
pricing:read
The pricing equivalent. targets has one entry per enabled pricing target, with state (published, failed, or never), the last success, the last attempt, next_scheduled, and in_flight. scheduled lists approved price batches that will publish the product’s prices; history lists one row per record, batch, and target, newest first, failed and skipped attempts included; skus rolls the same status up per SKU. history_limit (default 200, maximum 1000) caps the history, and truncated says whether it was cut.
From an agent, the MCP tools get_product_publish_status and get_price_publish_status answer “when, and to where, was this last published”, with who, batch, job, scheduled publishes, and history; both flag a partial history explicitly (truncated, size_limited, and a history_note) rather than letting an agent treat a cut list as complete. From the CLI, use mo api products get-publish-history and mo api pricing get-price-publish-status-by-product.

Per-target, per-environment outcomes

Under the hood, every record in a batch carries an outcome for each target and each environment it was published to. Because outcomes are keyed by both the target and its environment, a single record holds an independent QA result and production result without one overwriting the other. A recorded outcome captures what happened on that publish: A cancelled record was never sent, so it carries no response code, no attempts, and no error.

What each outcome means

The record was accepted by the destination. For a catalog record that means the product/SKU was created or updated; for a price record, the price was set. The record shows success and drops out of any pending-publish queue.
The destination rejected the record or a permanent error occurred — for example a variant with no matching SKU in the store. The outcome carries an error summary and a response code. Transient problems (rate limits, temporary server errors) are retried automatically first; only a record that can’t be published after retries lands as failed.
The record was intentionally not published — for instance a connector you built yourself decided the record does not apply to its system. Nothing went wrong, and nothing was published.A skip is terminal: it counts toward the batch’s completion, so a batch containing skipped records still reaches a final state. It is tracked in its own skipped count, never folded into published or failed. Because a skip is not a publish, it weighs against the batch’s success ratio the same way a failure does.
The publish was cancelled before this record was sent. Nothing was published for it and nothing went wrong with the record itself — it can be published again by re-driving the batch.A cancellation is the one outcome that does not move the batch’s published, failed, or skipped counts. It is counted on the job instead, and the cancellation itself moves the batch to its final state — the counts are never expected to add up on a cancelled run.

Batch-level rollup

As records report outcomes, the batch tracks published, failed, and skipped counts. Every record that publishes, fails, or is skipped is accounted for, and once they add up to the batch’s total the batch reaches a final state:
  • Published — at least 95% of the batch’s records succeeded.
  • Partial — fewer than 95% succeeded. Review the failed records’ error summaries, fix the underlying data, and publish again.
“Published” means the publish actually finished — never just started. A batch’s Published timestamp and publisher are stamped only once records have genuinely reported outcomes, not the moment a publish attempt began. If a catalog batch is sitting in a publishing state, its Published timestamp stays blank until that run completes; it no longer shows a timestamp (and “published by”) for a run that hasn’t reported a single outcome yet.
The 95% is measured against the batch’s total records, so failed and skipped records both count against it. The same threshold applies to both catalog and pricing batches.
Published does not mean every record succeeded. A batch flips to Published once 95% of its records succeed, so up to 5% of a Published batch may have failed — on a 500-record batch that is as many as 25 records that are not live. Always confirm against the per-record outcomes (open the batch and filter its records by outcome) rather than trusting the batch badge alone.
Publishing runs as a background job, so you can watch progress and see per-record errors on the Jobs page while a batch is in flight — and cancelling that job is how you stop a publish that is already under way.
The batch badge and the job status are two different readings, and they can disagree. The 95% threshold above decides the batch badge only. The job that did the publishing settles on its own counters: a catalog publish that got most of its records through reports Completed with Errors on the Jobs page while the batch reads Partial, and only a run that published nothing at all reports Failed. A mostly-successful publish used to report Failed here, because the batch’s Partial verdict was written onto the job; it no longer does. Size the re-drive from the job’s failed count, and use the per-record outcomes to see which records to fix.
A cancelled publish rolls up differently. Cancelled records move none of these counts, so a cancelled batch never reaches its total. Cancelling finalizes the batch directly instead: Partial if at least one record published, Failed if none did. Either way the batch is re-drivable — see Cancelling a catalog publish.
Batches sent to a self-built (poll) target roll up the same way. The outcome POST that completes the batch gets the final state back in its batch_final_status field — see build your own connector.
Some connectors surface warnings alongside outcomes — non-fatal issues that didn’t fail the publish but need attention, such as a brand or category that doesn’t exist in the destination yet (see VTEX). A warning doesn’t turn a success into a failed; it flags follow-up work.

Fixing a partial batch

Partial (or Failed) means some records didn’t make it. Fix the underlying data — the product, variant, or price record the errors point at — and then re-drive the batch:
A catalog Republish skips records that already published successfully — they are not re-sent, even if the product changed since. To push a corrected product that already published, put it in a new batch.
Republishing a catalog batch and pricing Publish now / Republish all require approval permission (catalog_batch:approve and pricing:approve respectively).
A catalog publish that keeps stalling stops retrying itself automatically. MerchantOps also detects a catalog publish whose records go quiet — nothing succeeds, fails, or is skipped — and finalizes it on its own so it isn’t left sitting in a publishing state forever. Normally that also puts it back in line to be re-attempted. If the same batch stalls this way repeatedly with still no records reporting an outcome, MerchantOps stops re-attempting it automatically after a few tries (three by default) rather than retrying indefinitely with nothing to show for it. From that point the batch needs a manual Republish once the underlying issue (for example, a publishing target that isn’t reachable) is resolved — republishing manually also resets the stall count.

Resetting a stuck pricing publish

A publish that never reports every record leaves the batch sitting in its publishing state, which also blocks Publish now (the batch looks like it is still in flight). Resetting it force-finalizes the batch so it can be published again:
It needs pricing:approve, and it never claims success: the batch lands Partial if at least one record published and Failed if none did. Both are re-drivable with Publish now. The reset is refused with a 409 when there is nothing to unstick — the batch is not publishing — or when the publish started too recently (roughly the last ten minutes), so a run that is genuinely still in flight is never interrupted. A poll connector can legitimately take minutes between fetching a record and reporting it; give it that time before resetting. The reset is recorded in the batch’s activity history.

Next steps

Publishing overview

How batches, targets, and scheduling fit together.

Build your own connector

Report outcomes from your own target and see them here.