# La spécification du flux produit OpenAI, champ par champ, avec exemples de rejet

> Neuf champs obligatoires, formats stricts (prix, booléens), alias hérités : chaque règle du flux produit OpenAI avec un exemple valide et un rejeté.

Canonical: https://convrail.com/fr/blog/specification-flux-produit-openai/

La spécification du flux produit OpenAI définit 9 champs obligatoires (item_id, title, description, url, brand, seller_name, image_url, availability, price), un ensemble de champs recommandés pour les variantes, les identifiants et les prix promotionnels, et des champs optionnels pour les attributs, les médias, la livraison, la publicité et le paiement. Les valeurs suivent des formats stricts : la monnaie en `79.99 USD`, les booléens en `true`/`false`, les identifiants sous forme de chaînes. Une ligne qui enfreint une règle est inutilisable, et la documentation ne décrit aucun rapport d'erreur ligne par ligne.

## Pourquoi la formulation exacte des règles compte

La [page de présentation du dépôt de fichiers](https://developers.openai.com/commerce/specs/file-upload/overview) cite les trois causes d'échec les plus fréquentes : « missing required fields », « outdated or non-spec field names » et « malformed field values ». Ce sont trois problèmes de format : le produit est bon, la ligne ne l'est pas. Comme la documentation ne décrit aucun rapport d'erreur ligne par ligne, un marchand ne découvre une ligne mal formée qu'en remarquant qu'un produit n'apparaît jamais dans ChatGPT. C'est pourquoi chaque règle ci-dessous est accompagnée d'un exemple rejeté. Les citations sont reprises mot pour mot de la [spécification du flux produit](https://developers.openai.com/commerce/product-feeds/spec) et de ses deux pages associées listées dans les sources.

## Les 9 champs obligatoires

Une ligne qui omet l'un de ces champs, ou qui fournit une valeur vide ou non reconnue, est inutilisable. La spécification est explicite pour availability : une valeur omise, vide ou non reconnue rejette la ligne.

| Champ | Type | Contrainte (d'après la spécification) | Exemple valide | Exemple rejeté |
| --- | --- | --- | --- | --- |
| `item_id` | chaîne | « Stable ID, unique per item or variant within your feed. Never reuse it for a different item. » | `TRAIL-BLK-10` | Un numéro de ligne de base de données qui change à chaque réimport |
| `title` | chaîne | « Product name, including the selected variant when relevant. » 150 caractères maximum, texte brut | `Trail running shoes, black, size 10` | Un titre de 300 caractères bourré de mots-clés |
| `description` | chaîne | « Factual product description for this item. » 5 000 caractères maximum, texte brut | `Waterproof trail shoes with a rubber outsole and mesh lining.` | `<p>Waterproof <strong>trail</strong> shoes</p>` |
| `url` | URL | « Product detail page for the item, with the variant selected when possible. Keep it stable. » HTTP ou HTTPS absolue, accessible publiquement | `https://example.com/products/trail?color=black&size=10` | `/products/trail` (chemin relatif) |
| `brand` | chaîne | « Product brand as shown on the product page. » Une vraie marque, pas une valeur de remplissage | `Northline` | `n/a` |
| `seller_name` | chaîne | « Name of the seller supplying this offer. » Un vrai nom, pas une valeur de remplissage | `Northline Outdoor` | `unknown` |
| `image_url` | URL | « Main product image, showing this variant. Use a direct image URL, such as a JPEG or PNG. » | `https://example.com/images/trail-black.jpg` | `https://example.com/products/trail` (une page, pas une image) |
| `availability` | énumération | « in_stock, out_of_stock, pre_order, backorder, or unknown. » | `in_stock` | `available`, `preorder`, `In Stock` |
| `price` | monnaie | « Regular item price in major currency units. » Format `amount CURRENCY` | `79.99 USD` | `79,99`, `$79.99`, `79.99`, `1,079.99 USD` |

Trois détails de ce tableau causent l'essentiel des ennuis.

**`item_id` doit survivre aux réimports.** Un identifiant dérivé d'une position de ligne ou d'un horodatage transforme chaque instantané en nouveau catalogue. Utilisez le SKU ou l'identifiant de variante de la plateforme, sous forme de chaîne.

**`availability` est une liste fermée.** Google Shopping utilise `preorder` ; OpenAI utilise `pre_order`. Un flux copié depuis un export Google porte la mauvaise orthographe sur chaque produit en précommande.

**`price` est une seule chaîne, pas deux colonnes.** La spécification demande « a decimal amount in major units, a space, and an uppercase three-letter ISO 4217 currency code », avec « a decimal point, no thousands separators or exponent notation, and no more fractional digits than the currency permits ». Un prix exporté depuis un environnement français sous la forme `25,99` échoue sur le seul séparateur décimal.

## Les champs recommandés

Ils ne sont pas obligatoires, mais une valeur mal formée dans un champ recommandé reste une valeur mal formée. Si vous ne pouvez pas renseigner un champ correctement, omettez-le.

### Variantes : `group_id`, `listing_has_variations`, `variant_dict`

Les trois champs fonctionnent ensemble. `group_id` est un « Stable parent-listing ID shared by all variants. » La spécification ajoute : « Omitted or empty: uses item_id, which does not establish a variant group. » Un `group_id` copié depuis `item_id` ne produit donc aucun groupe, sans que rien ne le signale.

`listing_has_variations` doit valoir `true` sur chaque ligne de variante : « Omitted, empty, or false: no variant options are used. »

`variant_dict` associe des noms d'options à des valeurs sélectionnées, sous forme de chaînes. Il « Requires listing_has_variations=true and group_id different from item_id. » Les clés et les valeurs doivent être non vides, les mêmes noms d'options doivent être utilisés dans tout le groupe, et chaque combinaison d'options doit être unique. La spécification demande aussi de garder les attributs de premier niveau comme `color` et `size` cohérents avec les mêmes options dans `variant_dict`, parce que « Neither representation reconciles conflicting values for you. »

| Situation | Résultat |
| --- | --- |
| `group_id` = `TRAIL`, `item_id` = `TRAIL-BLK-10`, `listing_has_variations` = `true`, `variant_dict` = `{"color":"Black","size":"10"}` | Groupe de variantes établi |
| `group_id` = `TRAIL-BLK-10` (identique à `item_id`) | Aucun groupe de variantes |
| `group_id` renseigné, `listing_has_variations` omis | Aucune option de variante utilisée |
| `variant_dict` = `{}` | Traité comme une absence d'options |
| `color` = `Blue` au premier niveau, `variant_dict.color` = `Black` | Valeurs contradictoires, non réconciliées |

### Identifiants : `gtin`, `mpn`, `offer_id`

`gtin` est « One assigned GTIN: exactly 8, 12, 13, or 14 digits, including a valid check digit. Preserve leading zeros; no spaces or dashes. » Deux conséquences : un GTIN avec une faute de frappe échoue au contrôle de la clé, et un GTIN exporté depuis un tableur qui a supprimé le zéro initial n'a plus la bonne longueur. Validez avant de téléverser.

`mpn` est le « Manufacturer-assigned part number, preserving its punctuation and casing. » La spécification est sans détour sur un raccourci courant : « do not invent a value to replace a missing GTIN. »

`offer_id` est un « Stable offer ID, unique within the feed. Use it to distinguish offers that share a product URL. » C'est une chaîne, donc les zéros initiaux sont conservés.

### État et prix promotionnel

`condition` accepte `new`, `refurbished` ou `used`. Une valeur omise ou vide « may be treated as new », donc précisez toujours `used` ou `refurbished` quand cela s'applique.

`sale_price` est le « Current sale price: greater than zero, strictly less than price, and in the same currency. » Un prix promotionnel « Nonpositive, equal, higher, or different-currency » n'est pas utilisé. La spécification indique aussi quand le mettre à jour : « Submit the current price; update the feed when a sale starts or ends. » Ne programmez pas une promotion à l'avance en envoyant le futur prix.

| `price` | `sale_price` | Résultat |
| --- | --- | --- |
| `79.99 USD` | `59.99 USD` | Utilisé |
| `79.99 USD` | `79.99 USD` | Non utilisé (égal) |
| `79.99 USD` | `59.99 EUR` | Non utilisé (devise différente) |
| `79.99 USD` | `0.00 USD` | Non utilisé (nul) |

### `is_eligible_search`

« true enables search eligibility; false disables it and checkout eligibility. Omitted or empty: true. » C'est l'interrupteur qui retire un produit rapidement : la page de présentation du dépôt recommande de passer `is_eligible_search=false` pour rendre un produit inéligible au prochain cycle de traitement, plutôt que de supprimer la ligne, parce qu'OpenAI « retains its most recently processed record for up to 14 days ».

## Les champs optionnels, par groupe

### Attributs de l'article

| Champ | Règle |
| --- | --- |
| `product_category` | « Your category path, from broad to specific, separated by > », par exemple `Apparel & Accessories > Shoes` |
| `material` | Matériaux principaux de l'article |
| `color` | Couleur sélectionnée, cohérente avec l'image du produit |
| `size` | Libellé de la taille sélectionnée ; utilisez `variant_dict` quand la taille distingue les variantes |
| `gender` | `male`, `female` ou `unisex` ; toute autre valeur équivaut à une absence de genre |
| `age_group` | `newborn`, `infant`, `toddler`, `kids` ou `adult` ; « a product attribute, not a purchase-age restriction » |
| `dimensions` | Objet avec des chaînes décimales positives pour au moins deux des valeurs length, width, height, plus une unité (`in`, `cm`, `ft`, `m`, `mm`) ; un objet vide est invalide |
| `weight` et `item_weight_unit` | Poids net positif sans emballage ; unité `g`, `kg`, `oz` ou `lb` ; l'unité est obligatoire avec le poids |

### Médias

`additional_image_urls` est un tableau en JSON ou en Parquet, et une chaîne séparée par des virgules en CSV ou en TSV. Les URL invalides sont omises. Si une URL d'image contient elle-même une virgule, la spécification demande de « Percent-encode commas as %2C in URL », sinon le délimiteur coupe l'URL en deux.

### Livraison et retours

| Champ | Règle |
| --- | --- |
| `shipping_price` | Format monétaire, même devise que price, non négatif ; « zero = no charge. Omitted/empty = unknown, not free » |
| `shipping` | Tuple `country:region:service_class:price`, en conservant la position vide de la région, par exemple `US::Standard:5.00 USD` |
| `accepts_returns` | `true` ou `false` ; omis signifie non précisé |
| `return_deadline_in_days` | Nombre entier positif, « Supply only with accepts_returns=true » |
| `return_policy` | URL HTTP ou HTTPS publique vers les conditions de retour ou de vente ferme |

### Avis et notes

`review_count` est un nombre entier non négatif d'avis produit (pas d'avis sur le vendeur ou la boutique) ; zéro signifie aucun avis, omis signifie inconnu. `star_rating` est une chaîne décimale sur une échelle de 0 à 5 avec deux décimales, par exemple `4.50`, et doit être accompagnée d'un `review_count` positif correspondant.

### Informations sur le marchand

`seller_url` pointe vers la vitrine ou la page de profil du vendeur (pour les offres de place de marché, la page du vendeur concerné). `marketplace_seller` nomme la place de marché où le paiement a lieu et nécessite une configuration avec OpenAI.

### Publicité

`is_ads_eligible` : « Set true for products Ads should process; false explicitly opts out. Omitted/empty: disabled unless feed-level default applies. » Renseignez-le explicitement plutôt que de compter sur une valeur par défaut. `ads_metadata` est un objet chaîne vers chaîne qui utilise les clés configurées pour votre intégration publicitaire, par exemple `{"custom_label_0":"summer"}` ; « Do not invent keys. »

### Paiement

`is_eligible_checkout` « true opts in only when search eligibility also true and checkout enabled. Omitted/empty/false: disabled. Search=false overrides this. » Quand vous activez cette option, publiez `seller_privacy_policy` et `seller_tos` sous forme d'URL publiques ; les fournir « does not establish checkout readiness » à lui seul.

### Ciblage géographique

`target_countries` est un tableau de codes ISO 3166-1 alpha-2 en majuscules « configured for feed. Omitted/empty does not mean worldwide. Requires market setup. » Ainsi `["US"]` est valide, `["United States"]` ou `["us"]` ne le sont pas. `store_country` est le pays de la boutique du vendeur sous forme de code ISO, pas une surcharge régionale de prix ou de stock.

## Les règles de données qui s'appliquent à tous les champs

Ces conventions viennent de la section « general conventions » de la spécification et s'appliquent quel que soit le format de fichier.

- **Omettez les valeurs inconnues.** « Omit an unknown value. Unless a row says otherwise, an omitted field, JSON null, or an empty delimited cell supplies no value. »
- **Aucune valeur de remplissage.** « Do not use placeholder strings such as null, unknown, or n/a; unknown is valid only where explicitly listed. » Le seul endroit où `unknown` est une valeur légale est `availability`.
- **Booléens.** « For boolean fields, use JSON true or false, or the lowercase strings true and false in delimited files. » `TRUE`, `1`, `yes` et `Y` ne sont pas des booléens.
- **Décimales.** Point décimal, aucun séparateur de milliers, aucune notation exponentielle.
- **Identifiants sous forme de chaînes.** « Keep identifiers as strings to preserve leading zeros. » Cela compte pour `gtin`, `item_id`, `offer_id` et `mpn`, et surtout en Parquet, où un outil d'écriture qui déduit une colonne entière supprime les zéros.
- **Texte et URL.** « Use UTF-8 text and absolute HTTP or HTTPS URLs; prefer HTTPS. »
- **Guillemets en CSV.** « In CSV, quote a cell containing commas, quotes, or newlines, and double each embedded quote. JSON objects in CSV or TSV cells must be serialized as JSON. » Un `variant_dict` dans une cellule CSV ressemble à `"{""color"":""Black"",""size"":""10""}"`.
- **Stabilité d'une mise à jour à l'autre.** Gardez `item_id`, `group_id` et `offer_id` stables quand le prix, le stock, le titre ou les images changent.

## Les alias hérités

Les anciens noms sont encore acceptés, mais la spécification demande de « Send only one name per value » et précise quel nom l'emporte quand les deux sont présents : « item_id wins over id and sku; group_id wins over item_group_id; the enable_ flags win over their is_eligible_ names. » Utilisez les noms actuels et n'émettez jamais l'alias dans le même fichier.

| Nom actuel | Alias hérité |
| --- | --- |
| `item_id` | `id`, `sku` |
| `group_id` | `item_group_id` |
| `is_eligible_search` | `enable_search` |
| `is_eligible_checkout` | `enable_checkout` |
| `is_ads_eligible` | `is_eligible_ads` |
| `return_deadline_in_days` | `return_window` |

Si votre flux dérive d'un export Google Shopping, ce sont les noms Google (`id`, `link`, `image_link`, `item_group_id`) qu'il faut convertir. La comparaison [Flux Google Shopping ou flux OpenAI](/fr/blog/flux-google-shopping-vs-flux-openai/) détaille cette correspondance.

## Une ligne JSONL valide

Une ligne par article. Cette ligne utilise les champs obligatoires, les champs de variante et quelques champs recommandés et optionnels. Notez que le titre sépare les détails de variante par une virgule, et que chaque booléen est un booléen JSON, pas une chaîne.

```json
{"item_id":"TRAIL-BLK-10","group_id":"TRAIL","listing_has_variations":true,"variant_dict":{"color":"Black","size":"10"},"offer_id":"northline-TRAIL-BLK-10","title":"Trail running shoes, black, size 10","description":"Waterproof trail shoes with a rubber outsole and mesh lining. Lace closure, 320 g per shoe.","url":"https://example.com/products/trail?color=black&size=10&utm_medium=feed","brand":"Northline","seller_name":"Northline Outdoor","image_url":"https://example.com/images/trail-black.jpg","additional_image_urls":["https://example.com/images/trail-black-side.jpg"],"availability":"in_stock","price":"79.99 USD","sale_price":"59.99 USD","gtin":"00012345678905","condition":"new","color":"Black","size":"10","product_category":"Apparel & Accessories > Shoes","is_eligible_search":true,"is_ads_eligible":true,"target_countries":["US"]}
```

Le paramètre `utm_medium=feed` sur `url` suit la [page des bonnes pratiques](https://developers.openai.com/commerce/guides/best-practices), qui suggère d'ajouter « feed attribution parameters to url (for example utm_medium=feed) » afin de distinguer les clics venant du flux dans vos outils d'analyse. La même page demande un texte « concise, factual copy » dans les titres et les descriptions, et de garder « title, url, description, media, availability, and price variant-specific when those values differ ».

## Erreurs fréquentes

- Exporter `price` et `currency` en deux colonnes, ou avec une virgule décimale locale.
- Copier `group_id` depuis `item_id`, ce qui ne produit aucun groupe de variantes.
- Écrire `TRUE`/`FALSE` ou `1`/`0` dans les colonnes booléennes.
- Remplir `brand` ou `seller_name` avec `n/a` pour passer un contrôle « obligatoire » dans un outil interne ; la spécification traite cette valeur de remplissage comme invalide.
- Laisser le HTML de l'éditeur de la boutique dans `description`.
- Envoyer `id` et `item_id` dans le même fichier.
- Laisser un tableur convertir `gtin` en nombre et supprimer le zéro initial.
- Passer `is_eligible_checkout=true` sur un produit dont `is_eligible_search` vaut `false`.

## Comment Convrail valide chaque règle avant la livraison

Convrail applique ces règles avant qu'un fichier n'atteigne OpenAI, de sorte qu'une ligne cassée vous est signalée au lieu de disparaître :

- Les 9 champs obligatoires sont contrôlés en présence et en format sur chaque ligne : la monnaie en `amount CURRENCY`, `availability` face à la liste fermée, `url` et `image_url` comme URL HTTP ou HTTPS absolues pointant vers un JPEG ou un PNG direct. Convrail plafonne aussi `item_id` à 100 caractères et `brand`, `seller_name` et `mpn` à 70, et rejette les titres écrits entièrement en majuscules.
- Les valeurs de remplissage (`null`, `unknown`, `n/a`) dans `brand` et `seller_name` sont rejetées.
- Les champs conditionnels sont validés quand ils sont présents : `sale_price` strictement inférieur à `price` dans la même devise, `group_id` différent de `item_id`, `is_eligible_checkout` exigeant l'éligibilité à la recherche plus les deux URL de politique, `target_countries` en codes alpha-2 majuscules, `star_rating` entre 0 et 5.
- La clé de contrôle du GTIN est vérifiée. Un GTIN invalide est omis plutôt qu'envoyé, la ligne reste donc valide ; l'omission est consignée dans le journal.
- Le HTML est retiré des descriptions, et les booléens sont émis comme booléens JSON en JSONL et en Parquet, et comme chaînes en minuscules en CSV et en TSV.
- Chaque rejet est enregistré avec l'`item_id`, le champ et la règle, dans un journal d'exécution qui compte aussi les articles lus, acceptés et rejetés.

Le même catalogue peut aussi être projeté en flux compatible Google. Si vos produits manquent déjà dans les résultats de ChatGPT, [Pourquoi vos produits n'apparaissent pas dans les résultats shopping de ChatGPT](/fr/blog/produits-absents-resultats-shopping-chatgpt/) relie chaque symptôme à une règle ci-dessus, et [Parquet, JSONL ou CSV](/fr/blog/parquet-jsonl-csv-flux-openai/) couvre les choix au niveau du fichier.

## Prochaine étape

Connectez votre boutique et laissez Convrail valider votre catalogue face à chaque règle de cette page avant la première livraison : voir la [page flux produit](/fr/flux-produit/).