Why your products do not show up in ChatGPT shopping results: a 10-point checklist

Products missing from ChatGPT shopping usually trace to a malformed feed row. Ten causes, from a bad price format to a stale snapshot, and how to fix each.

Published 10 min read

Products missing from ChatGPT shopping results almost always trace to a feed row that OpenAI could not use: a required field is missing, a price is written 25,99 instead of 25.99 EUR, a brand says n/a, a GTIN fails its check digit, or the snapshot stopped refreshing. The specification documents no per-row error report, so you have to audit the file yourself. Here are the 10 causes to check, in order.

Why you have to audit the file yourself

The file upload overview lists three common failure causes: “Missing required fields”, “Outdated or non-spec field names” and “Malformed field values”. It also asks you to “Start with a small sample (around 100 items)” before a full deployment. What the documentation does not describe is a report that tells you which rows were rejected and why. Until such a report exists, a product that disappears from ChatGPT is a symptom you have to trace back to a row in your file.

This checklist goes from the most frequent row-level causes to the file-level causes that take a whole catalog offline at once. For each, you get a detection method you can run on your own export and the fix. Field rules are quoted from the product feed specification; the full field reference is in The OpenAI product feed specification, explained field by field.

1. A required field is missing or empty

The nine required fields are item_id, title, description, url, brand, seller_name, image_url, availability and price. The specification says an omitted field, a JSON null or an empty delimited cell “supplies no value”, and for availability it states that omitted, empty or unrecognized values reject the row.

How to detect. Count empty cells per required column in your export. The usual culprits are brand (empty vendor field on Shopify, empty attribute on WooCommerce), description (products created quickly with a title only) and seller_name (never mapped because no other channel asks for it).

How to fix. Fill the value at the source. Do not fill it with a placeholder to pass the check; that is cause number 4.

2. Outdated or non-spec field names

Feeds derived from an older OpenAI template or from a Google Shopping export carry names OpenAI treats as legacy aliases (id, sku, item_group_id, enable_search, enable_checkout, is_eligible_ads, return_window) or names it does not know at all (link, image_link, google_product_category). Aliases are still accepted, but the specification asks you to “Send only one name per value”, and unknown names supply nothing, so a required field spelled the Google way is a missing required field.

How to detect. Compare your header row (or your JSON keys) to the current names: item_id, group_id, is_eligible_search, is_eligible_checkout, is_ads_eligible, return_deadline_in_days, url, image_url.

How to fix. Rename in the export mapping, and never emit both the current name and its alias in the same file.

3. Price or sale price in the wrong format

price is one string: “a decimal amount in major units, a space, and an uppercase three-letter ISO 4217 currency code”, for example 79.99 USD, with “a decimal point, no thousands separators or exponent notation”. sale_price must be “greater than zero, strictly less than price, and in the same currency”; an equal, higher, nonpositive or different-currency sale price is not used.

How to detect. Search the price column for a comma, a currency symbol (, $, £), a lowercase currency code, a missing currency, or a value above 999 written with a thousands separator. Then compare sale_price to price row by row and flag every case where they are equal.

How to fix. Format prices in the export, not in the store. A store that displays 1 079,99 € is fine; the feed must say 1079.99 EUR. A sale that ended should remove sale_price from the row: “Submit the current price; update the feed when a sale starts or ends.”

4. Placeholder strings

“Do not use placeholder strings such as null, unknown, or n/a; unknown is valid only where explicitly listed.” The only field where unknown is legal is availability. Placeholders usually appear in brand and seller_name because an internal tool insisted the columns be non-empty.

How to detect. Search every text column for null, unknown, n/a, na, none, - and TBD, case-insensitive.

How to fix. Replace with the real value. For brand, the specification wants the “Product brand as shown on the product page”; for private-label products, that is your own brand.

5. HTML, all-caps or over-long titles and descriptions

title is limited to 150 characters and description to 5,000, both plain text. The best practices page asks for “concise, factual copy that helps users understand products. Plain text and bullet-style text are both acceptable.” Store editors save descriptions as HTML, and an export that does not strip tags ships <p>, <br>, &nbsp; and inline styles inside the field.

How to detect. Search the description column for < and &. Measure title length and flag anything above 150 characters. Flag titles where every letter is a capital.

How to fix. Strip tags and decode entities in the export step, keep the paragraph breaks as line breaks. Rewrite titles in sentence case with the variant details included, since the specification wants the “Product name, including the selected variant when relevant.” The specification does not name all-caps as a rejection cause; Convrail’s validator rejects it anyway, because an all-caps title reads as shouting inside a conversational answer and is never how a real product page names the product. If you want help rewriting titles at scale, the feed optimization module drafts them for your review.

6. The image URL is not a direct JPEG or PNG

image_url must be a “direct image URL, such as a JPEG or PNG”, publicly accessible, HTTPS preferred. Three patterns fail: a link to the product page instead of the image file, a URL that needs a session or a signed token to load, and a format other than JPEG or PNG (WebP or AVIF served by an image CDN by default).

How to detect. Fetch a sample of image URLs from outside your network with no cookies and check the Content-Type header: it should be image/jpeg or image/png. Flag URLs ending in .webp or .avif, and URLs containing ?token= or &signature=.

How to fix. Point image_url at the original JPEG or PNG asset. Most platform CDNs keep the original when you request the URL without a format parameter.

7. Invalid GTIN

The gtin must be “exactly 8, 12, 13, or 14 digits, including a valid check digit. Preserve leading zeros; no spaces or dashes.” A wrong GTIN is a malformed value on an otherwise good row.

How to detect. Three checks: length in {8, 12, 13, 14}; digits only (no hyphens, no spaces); and the check digit itself, which you can recompute with the standard modulo-10 weighting (multiply alternating digits by 3 and 1 from the right, sum, and the check digit brings the total to a multiple of 10). Also look for 11-digit or 12-digit codes that used to be 12 or 13: a spreadsheet that treated the column as a number dropped the leading zero.

How to fix. Correct the barcode at the source, or omit the field. The specification says explicitly “do not invent a value to replace a missing GTIN.” Convrail verifies the check digit on every row and omits an invalid GTIN rather than sending it, so the product still ships and the omission is journaled.

8. Variant grouping errors

Variants need three fields to agree. group_id is the “Stable parent-listing ID shared by all variants”; if it is “Omitted or empty: uses item_id, which does not establish a variant group.” listing_has_variations must be true on every variant row. variant_dict maps option names to values and “Requires listing_has_variations=true and group_id different from item_id.”

How to detect. Flag rows where group_id equals item_id. Flag groups where listing_has_variations is missing or false on some rows. Flag groups whose variant_dict keys differ between rows (one variant says colour, the next says color) or where two rows share the same option combination. Compare top-level color and size with the same keys in variant_dict; the specification warns that “Neither representation reconciles conflicting values for you.”

How to fix. Use the platform’s parent product identifier as group_id and the variant identifier as item_id. Emit the same option names across the group. Keep title, url, image_url, availability and price variant-specific, as the best practices page recommends.

9. Eligibility and targeting flags

Several flags can hide a product on purpose or by accident.

  • is_eligible_search=false “disables it and checkout eligibility”. If a bulk edit set it on the whole catalog, nothing is eligible.
  • is_ads_eligible “Omitted/empty: disabled unless feed-level default applies.” Products you expect in ChatGPT ads need true explicitly.
  • is_eligible_checkout=true is ignored when search eligibility is false, and the two policy URLs (seller_privacy_policy, seller_tos) do not by themselves establish checkout readiness.
  • target_countries must be uppercase ISO 3166-1 alpha-2 codes “configured for feed. Omitted/empty does not mean worldwide.” A country name, a lowercase code, or a country not configured in your setup does not target anything.

How to detect. Count false values in is_eligible_search. List distinct values in target_countries and compare them to what is configured with OpenAI. Check that boolean columns contain only true or false (in delimited files, lowercase strings; in JSONL and Parquet, real booleans), not TRUE, 1 or yes.

How to fix. Set the flags deliberately from feed settings rather than per product, and keep them out of spreadsheet formulas that output TRUE.

10. The snapshot is stale, oversized or renamed

The last group of causes takes a whole catalog offline rather than one product.

  • Not refreshed. The overview asks you to “Publish full snapshots on a predictable cadence (at least daily).” OpenAI “retains its most recently processed record for up to 14 days”, so a feed whose cron job died stays visible for a while, then everything expires at once. Conversely, a product you removed from the file lingers up to 14 days; to remove it sooner, keep the row and set is_eligible_search=false.
  • Wrong file naming. “Use a stable file name. Keep the same file name on every update and overwrite it with the latest snapshot instead of creating a new name each run.” A run that writes feed-2026-09-05.jsonl.gz next to yesterday’s file does not replace yesterday’s snapshot.
  • Oversized shards. “Up to 500k items per shard is recommended; target shard files under ~500MB”. Split before those limits.
  • Not UTF-8. A Windows-1252 export with accented characters is a malformed value on every affected row.
  • Full deployment without a sample. The overview asks you to start “with a small sample (around 100 items)”. A hundred rows expose every mapping error with a fraction of the noise.

How to detect. On your SFTP destination, list the files: there should be one stable name per shard, overwritten daily, each under the size limit. Check the last modification time. Run file on the export to confirm UTF-8.

How to fix. Fix the scheduler and naming in the export job. Convrail writes shards as feed-organic-000.<ext>, feed-organic-001.<ext> and so on, splits at 500,000 items or about 450 MB, and overwrites the same names on every daily delivery; the format trade-offs are covered in Parquet vs JSONL vs CSV for OpenAI product feeds.

Common mistakes when diagnosing

  • Checking the store, not the file. The product page looks perfect; the exported row is what OpenAI reads.
  • Testing one product and generalizing. A cause like a decimal comma affects every row exported from the same locale.
  • Deleting a product from the feed to “reset” it. It persists up to 14 days; the reset does nothing visible.
  • Fixing the value in the file by hand. The next automated snapshot overwrites the fix; correct the mapping or the source data.
  • Assuming silence means success. With no per-row report described in the documentation, a delivery that uploaded without error tells you the transfer worked, not that the rows were usable.

Where the Convrail run journal fits

Convrail runs this checklist automatically on every row before the file leaves. Each run records itemsTotal, itemsValid and itemsRejected, the diff against the previous run (added, removed, changed), the delivery status with the attempt count, and one error entry per rejected item with the item_id, the field and the rule it broke. The nine required fields, the price and sale price formats, placeholders, HTML in descriptions, all-caps titles, image URLs, the GTIN check digit, the variant rules and the eligibility flags are all covered, and the shards are named, sized and delivered as the overview asks. When a product you sell every day is not in ChatGPT, the journal tells you which run dropped it and why, instead of leaving you to reverse-engineer a silence. A failed delivery retries, then opens a health alert.

What to do next

Run your catalog through Convrail’s validator and read the rejection journal before your next snapshot: see the product feed page.

Sources

Frequently asked questions

Does OpenAI tell me which products were rejected from my feed?

The specification documents no per-row error report. The file upload overview names the common failure causes (missing required fields, outdated field names, malformed values), so you have to audit the file yourself before delivery.

How long does a product stay in ChatGPT after it disappears from my feed?

OpenAI retains its most recently processed record for up to 14 days. To remove a product faster, keep it in the feed with is_eligible_search set to false.

Can a single bad field remove a product from ChatGPT shopping?

Yes. A malformed value in any field, required or not, makes the row unusable, so one wrongly formatted price or one HTML tag in a description is enough. The fix is to validate every row before delivery.

Why does my whole catalog seem missing rather than a few products?

Check the file-level causes first: a feed that uses a new file name every run, shards over the recommended size, a snapshot older than 14 days, or legacy field names across every row. These affect every product at once.

Measure the ChatGPT channel this week

Free during early access while we onboard the first stores. Leave your email and we send the install link when your spot opens.

No newsletter. One email with the install link, nothing else.