ChatGPT Product Feed Setup: Fields, Formats and SFTP Delivery
ChatGPT product feed setup in 7 steps: the 9 required fields, accepted formats, SFTP delivery, variants, refresh rules and mistakes that get rows rejected.
Long Nguyen
Lập trình viên Fullstack · Kỹ sư AI · Nhà nghiên cứu
What a ChatGPT product feed is and who can submit one
A ChatGPT product feed is a structured catalog file that you send to OpenAI so ChatGPT can read your products, prices and stock status. Delivery is a push: you deliver files on a schedule instead of waiting to be crawled, and the quality of that file is what everything else depends on.
Access is the first gate. As of , OpenAI's Get Started guide says product feed onboarding is available to approved partners only, and applications go through the merchant form at chatgpt.com/merchants. Apply first, then use the waiting time to clean your data, because most of the setup work is catalog hygiene that any feed will need.
There are two delivery routes, file upload and an API. OpenAI generally recommends sending the entire feed once a day by file upload and pushing updates through the day via the API. Small feeds can do everything through the API, and promotions data can only be sent through the API. This guide covers the file-upload route, checked against OpenAI's file upload overview.
ChatGPT product feed setup in 7 steps
- Apply for access through OpenAI's merchant form and confirm your delivery details (SFTP location and, if you use them, Ads Manager or API options) during onboarding.
- Pick the route: file upload, API, or both. A daily full snapshot plus intraday API updates is the pattern OpenAI recommends.
- Map your catalog to the nine required fields (next section). One row per purchasable item or variant.
- Export in a supported format: Parquet is preferred, and jsonl.gz, csv.gz and tsv.gz are also accepted. Encode everything as UTF-8.
- Send a sample of roughly 100 items with every required field present, and read the upload results before scaling up.
- QA the first full snapshot, not just the sample. Sample-only testing hides problems that appear at catalog scale, such as duplicate IDs and odd variants.
- Automate the daily snapshot, overwriting the same file name each time.
The nine required fields for a ChatGPT product feed
OpenAI's product spec lists nine fields as required for a useful discovery feed. Use a real brand and seller name, never placeholders. Details below follow the OpenAI products spec.
| Field | What to send | Trap to avoid |
|---|---|---|
item_id |
Stable ID, unique per item or variant | Never reuse it for a different item. OpenAI matches products by this ID, not by file name. |
title |
Product name, including the selected variant | Aim for 150 characters or fewer. |
description |
Factual plain-text description of this item | Aim for 5,000 characters or fewer. No marketing filler. |
url |
Product page, with the variant selected when possible | Must be publicly accessible and stable. |
brand |
Brand as shown on the product page | Placeholders are not acceptable. |
seller_name |
The seller users should see | Required on every row in the OpenAI format. |
image_url |
Direct JPEG or PNG URL showing this variant | Must be publicly accessible, not a gallery page. |
availability |
in_stock, out_of_stock, pre_order, backorder or unknown |
Omitted, empty or unrecognized values reject the row. |
price |
Regular price as 79.99 USD |
Major units, a space, an uppercase ISO 4217 code, no thousands separators. |
A valid one-line JSONL record looks like this:
{"item_id":"GRIND-HAND-BLK","title":"Hand coffee grinder, matte black","description":"Manual burr grinder with a stainless steel burr and a 30 g glass jar.","url":"https://example.com/products/hand-grinder-black","brand":"Brewline","seller_name":"Brewline Coffee Gear","image_url":"https://example.com/images/hand-grinder-black.jpg","price":"64.00 USD","availability":"in_stock"}
One expert caution: the spec says format guidance is not a guarantee that every invalid value is rejected at upload. A file that uploads without errors can still carry bad data, so validate before you send, not after. A small checker is included further down.
File formats, SFTP delivery and file rules
| Topic | OpenAI guidance |
|---|---|
| Feed type | Full snapshot of the whole catalog, treated as the source of truth |
| Cadence | At least daily |
| Delivery | Push to OpenAI via SFTP |
| Formats | Parquet preferred (ideally zstd-compressed); jsonl.gz, csv.gz and tsv.gz also supported |
| Encoding | UTF-8 |
| File names | Keep one stable name and overwrite it every run |
| Shards | Up to 500k items per shard, files under about 500 MB; keep the same shard set on every update |
Two behaviors matter in practice. First, the SFTP root directory represents your entire catalog and there is no completion marker: OpenAI processes every file in the root once uploads stop changing for a short time. Upload all shards back to back, because a long pause can trigger processing of an incomplete catalog (OpenAI then reprocesses once the rest arrive). Second, if several brand feeds share one location, prefix file names with the brand.
Some third-party guides describe delivery as an HTTPS push. OpenAI's own overview says SFTP, so follow that and confirm the exact endpoint with OpenAI during onboarding.
The heavy part is rarely the export itself. It is keeping one clean catalog feeding several channels at once, which is the point where handing over product feed management usually costs less than maintaining custom scripts per channel.
Variants, group IDs and GTIN validation
Send one row per selectable variant. Each row has its own item_id, the same group_id (which must differ from every item ID), listing_has_variations=true and a variant_dict of the selected options. Each row carries its own price, availability, URL and images.
{"item_id":"TEE-NAVY-M","group_id":"TEE","listing_has_variations":true,"variant_dict":{"color":"Navy","size":"M"},"offer_id":"brewline-TEE-NAVY-M","title":"Cotton tee, navy, size M","description":"Midweight cotton tee.","url":"https://example.com/products/tee-navy-m","brand":"Brewline","seller_name":"Brewline Coffee Gear","image_url":"https://example.com/images/tee-navy.jpg","price":"24.00 USD","availability":"in_stock"}
{"item_id":"TEE-NAVY-L","group_id":"TEE","listing_has_variations":true,"variant_dict":{"color":"Navy","size":"L"},"offer_id":"brewline-TEE-NAVY-L","title":"Cotton tee, navy, size L","description":"Midweight cotton tee.","url":"https://example.com/products/tee-navy-l","brand":"Brewline","seller_name":"Brewline Coffee Gear","image_url":"https://example.com/images/tee-navy.jpg","price":"24.00 USD","availability":"out_of_stock"}
Rules that catch teams out: use identical option names across a group, keep top-level color or size consistent with variant_dict because neither reconciles conflicts for you, and never put the price inside an offer_id. Legacy Custom_variant1_category and option pairs still work, but do not mix them with variant_dict.
For identifiers, gtin must be exactly 8, 12, 13 or 14 digits with a valid check digit, kept as a string so leading zeros survive. Submit mpn together with brand, and do not invent an MPN to fill a missing GTIN. This pre-upload checker covers the required fields, the availability values, the money format, GTIN check digits and duplicate IDs, and writes a gzipped JSONL snapshot:
import gzip
import json
import re
REQUIRED = ("item_id", "title", "description", "url", "brand",
"seller_name", "image_url", "availability", "price")
AVAILABILITY = {"in_stock", "out_of_stock", "pre_order", "backorder", "unknown"}
PRICE = re.compile(r"^\d+(\.\d+)? [A-Z]{3}$")
def gtin_ok(value):
if not value.isdigit() or len(value) not in (8, 12, 13, 14):
return False
body, check = value[:-1], int(value[-1])
total = sum(int(d) * (3 if i % 2 == 0 else 1)
for i, d in enumerate(reversed(body)))
return (10 - total % 10) % 10 == check
def problems(row):
out = [f"missing {f}" for f in REQUIRED if not str(row.get(f, "")).strip()]
if row.get("availability") not in AVAILABILITY:
out.append("availability would reject the row")
if not PRICE.match(str(row.get("price", ""))):
out.append("price must look like 79.99 USD")
if row.get("gtin") and not gtin_ok(str(row["gtin"])):
out.append("gtin fails the check digit")
return out
def write_snapshot(rows, path="products.jsonl.gz"):
seen, failed = set(), 0
with gzip.open(path, "wt", encoding="utf-8") as f:
for row in rows:
issues = problems(row)
if row.get("item_id") in seen:
issues.append("duplicate item_id")
seen.add(row.get("item_id"))
if issues:
failed += 1
print(row.get("item_id"), issues)
continue
f.write(json.dumps(row, ensure_ascii=False) + "\n")
return failed
Search and checkout eligibility flags
| Field | Default | What it does |
|---|---|---|
is_eligible_search |
true when omitted | false removes the product from search eligibility and also disables checkout. Eligibility does not guarantee display. |
is_eligible_checkout |
disabled when omitted | Opts in only when search is also true and checkout is enabled for your integration. Needs seller_privacy_policy and seller_tos URLs. |
Setting a checkout flag does not complete checkout onboarding, since checkout requires a separately enabled integration. Legacy aliases such as enable_search and enable_checkout are still accepted, but send only one name per value: the enable_ version wins over the is_eligible_ version, which produces confusing results if an old export script is still writing both.
Also check the prohibited products policy before uploading. OpenAI excludes categories such as adult content, alcohol, nicotine, gambling, weapons and prescription-only medications, and it may remove products or ban a seller that violates the policy. Filter them out of the feed or set search eligibility to false.
Optional fields worth sending, and the US-only default
| Field | Rule | Trap |
|---|---|---|
sale_price |
Above zero, strictly below price, same currency |
An equal, higher or different-currency sale price is ignored. |
shipping_price |
Non-negative, same currency as price | Omitted means unknown, not free shipping. |
accepts_returns, return_deadline_in_days, return_policy |
Boolean, whole days, public URL | A policy URL does not set accepts_returns. |
review_count, star_rating |
Product reviews only, 0 to 5 rating | Do not mix in store or seller reviews. |
additional_image_urls |
Array in JSONL, comma-separated in CSV | Percent-encode commas inside a URL as %2C. |
The market default deserves attention. The standard OpenAI-format upload currently targets the US, and row-level columns such as target_countries do not change that. Other markets need OpenAI to confirm the integration first, and a currency, product URL or sizing system does not select a destination. If you sell mainly outside the US, ask about market support during onboarding before you invest in localized feeds.
How often to update the feed and how removal works
Publish full snapshots at least daily. A product missing from a processed snapshot is not deleted immediately: OpenAI keeps its last processed record for up to 14 days, which protects you from a delayed shard. That has two consequences. To pull a product from search on the next processing run, set is_eligible_search=false. To remove it by omission, leave it out of every shard in later full snapshots and expect the retained record to expire within 14 days.
Date fields do not schedule anything. Sale windows, expiration dates and availability dates do not switch prices or stock automatically, so update the price when a sale starts and ends, and update availability when an item sells out. Because products are matched by item_id, generate IDs from your stable SKU or database key, never from a row number or a title slug that changes when someone edits the name.
Can you reuse a Google Merchant Center feed for ChatGPT?
Only when OpenAI confirms the Google-compatible format for your registered feed. It is a separate profile with different rules, and the OpenAI format is checked first, with one format applying to the whole upload.
| Aspect | OpenAI format | Google-compatible profile |
|---|---|---|
| Files | Parquet, jsonl.gz, csv.gz, tsv.gz | .txt, .tsv or .csv, optionally gzipped; JSON and XML not supported |
| Pre-order value | pre_order |
preorder |
unknown availability |
Accepted | Not accepted |
| Seller identity | seller_name on every row |
Registered merchant name; an uploaded seller_name cannot override it |
| Per-item search opt-out | Honored through is_eligible_search |
Not honored; upload only products meant for discovery |
| Identifiers | GTIN and MPN optional | Valid GTIN or MPN needed unless identifier_exists is false |
The practical lesson is that a Google export is a starting point, not a drop-in. The spelling of preorder, the missing opt-out and the registered seller name are exactly the differences that surface late.
Common ChatGPT feed mistakes and a pre-upload checklist
- Price in the wrong shape. Write
79.99 USD, not7999or79,99. - Bad availability values. An unrecognized value rejects the row. Use
unknownexplicitly rather than leaving it blank. - Placeholder strings. Do not write
null,n/aorunknowninto optional fields. Omit the field. - Invalid GTINs. Check the digit and keep leading zeros by treating the value as a string.
- New file names each run. Overwrite one stable name or shard set instead.
- Two shipping representations. Do not send both
shipping_priceand ashippingtuple for the same charge, and note that the tuple needs feed-specific setup. - Private or blocked URLs. Product and image URLs must be publicly accessible.
Getting the feed built and kept current
Getting a feed accepted once is the easy part. Keeping IDs stable, prices and stock fresh and variants grouped correctly across every update is what protects your listings over time. Netalith builds and maintains product feeds for Google Merchant Center, Meta, Pinterest, Microsoft and OpenAI (ChatGPT). If you want a second opinion on your catalog or a feed built for you, request a free quote and describe your platform and catalog size.
CÂU HỎI THƯỜNG GẶP
Câu hỏi thường gặp
Can any merchant submit a product feed to ChatGPT?
Not yet. As of September 29, 2026, OpenAI says product feed onboarding is available to approved partners, and you apply through the merchant form at chatgpt.com/merchants. Prepare and validate your data while you wait.
What file format should I use for a ChatGPT product feed?
OpenAI prefers Parquet, ideally with zstd compression. It also supports jsonl.gz, csv.gz and tsv.gz. All files must be UTF-8, and JSONL is usually the easiest for small and mid-size catalogs.
How often should I update my ChatGPT product feed?
Send a full snapshot at least daily, overwriting the same file name each time. OpenAI's Get Started guide also recommends pushing updates during the day through the API when prices or stock change quickly.
Does uploading a feed guarantee my products appear in ChatGPT?
No. Eligibility does not guarantee display. The feed makes products eligible; whether ChatGPT shows them depends on the query and the quality and relevance of your data.
How do I remove a product from the ChatGPT feed?
Set is_eligible_search to false to make it ineligible the next time a snapshot is processed, or omit it from every shard in later full snapshots. OpenAI keeps the last processed record for up to 14 days, so removal by omission is not instant.
Do I need GTINs to set up a ChatGPT product feed?
In the OpenAI format, GTIN and MPN are optional. If you send a GTIN it must be valid, with 8, 12, 13 or 14 digits and a correct check digit. Do not invent an MPN to fill a missing GTIN. The Google-compatible profile requires a valid GTIN or MPN unless identifier_exists is false.
Can I use my Google Merchant Center feed for ChatGPT?
Only if OpenAI confirms the Google-compatible format for your registered feed. Its rules differ from the OpenAI format: preorder is spelled differently, unknown availability is rejected, and the seller name comes from your registered merchant name.