Products
Poussez votre catalogue produits vers Humind pour que l'assistant IA puisse recommander les bons items, ancrer ses réponses dans vos données et afficher des prix et disponibilités exacts.
Les endpoints ici couvrent create, read, update, replace, archive et batch sur les produits, y compris leurs variants et traductions embarquées.
L'objet Product
| Champ | Type | Requis | Description |
|---|---|---|---|
external_id | string | Oui (à la création) | Votre identifiant unique pour ce produit. Free-form, doit être unique par company. Utilisé comme clé d'upsert : reposter avec la même valeur met à jour le produit existant. |
humind_id | string | Renvoyé seulement | 24 caractères hex ObjectId qu'Humind assigne. Stable pendant toute la vie du produit. |
title | string | Oui | Titre d'affichage dans default_language. |
description | string | Non | Description en texte brut. |
description_html | string | Non | Description HTML. Sanitizée côté serveur, voir HTML content. |
handle | string | Non | Slug URL-safe, minuscule, séparé par tirets. Optionnel : quand absent, le serveur le dérive du title (ASCII en minuscule, les caractères non alphanumériques remplacés par des tirets). Pas obligé d'être unique dans le store ; les clés de lookup sont external_id / humind_id, pas le handle. |
type | 'product' | 'kit' | Non | Défaut product. Utilisez kit pour les articles qui regroupent plusieurs SKU vendus comme un ensemble (rare). Toute autre valeur est rejetée avec une erreur 400 validation_failed sur type. |
status | 'active' | 'archived' | 'draft' | 'unlisted' | Non | Défaut active. archived cache un produit retiré, draft sert à préparer les produits non publiés et unlisted laisse un produit disponible via son URL directe tout en l'excluant de la découverte et des recommandations Humind. |
online_store_url | string | Non | URL publique du produit sur votre storefront. L'assistant l'utilise pour deep-linker les acheteurs depuis le chat. |
default_language | string | Non | Tag BCP 47 court (en, fr, pt-BR). Le sous-tag primaire de deux lettres est requis ; le sous-tag de région optionnel doit être en majuscules. Si omis, la langue principale de la company est utilisée. Les autres formes sont rejetées avec validation_failed. |
available_for_sale | boolean | Renvoyé seulement | Calculé automatiquement à partir des variants : true quand status vaut active ou unlisted et qu'au moins un variant a available_for_sale: true. Toute valeur envoyée sur le produit lui-même est ignorée. Pour rendre un produit indisponible, mettez available_for_sale: false sur tous ses variants, ou archivez-le (status: archived). |
brand | object | Non | Voir Brand. |
categories | string[] | Non | Noms de catégories free-form. Utilisé pour un soft-grouping ; n'a pas à correspondre aux collections Humind. |
images | object[] | Non | Voir Image. La première image est la principale dans le chat. |
metafields | object[] | Non | Métadonnées produit personnalisées. Voir Metafields. Maximum 250 entrées. |
group_memberships | object[] | Non | Familles de produits auxquelles ce produit appartient. Voir Appartenances aux groupes de produits. Maximum 20 entrées. |
variants | object[] | Oui (1 à 250) | Voir Variant. Même les produits mono-SKU ont besoin d'un variant qui porte prix et currency. Les external_id de variants doivent être uniques au sein du tableau ; les doublons sont rejetés avec validation_failed. |
translations | object | Non | Map de codes ISO 639-1 vers les overrides par locale. Voir Translations. |
created_at | ISO 8601 | Renvoyé seulement | Timestamp de création (UTC). |
updated_at | ISO 8601 | Renvoyé seulement | Timestamp de dernière modification (UTC). |
Disponibilité commerciale et découverte Humind sont distinctes
Un produit unlisted peut renvoyer available_for_sale: true, rester accessible sur le storefront du marchand et être malgré tout exclu de l'indexation Humind. Humind ne génère ni vecteur de recherche ni questions IA pour ce produit et ne le propose pas dans les recherches ou recommandations.
Brand
| Champ | Type | Requis | Description |
|---|---|---|---|
name | string | Oui | Nom d'affichage de la marque. |
domain | string | Non | Domaine web canonique de la marque (sans protocole). Utilisé pour la déduplication et le contexte assistant. |
Image
| Champ | Type | Requis | Description |
|---|---|---|---|
url | string | Oui | URL HTTPS publiquement accessible. Humind récupère et cache l'image ; assurez-vous que l'URL est stable. |
alt | string | Non | Texte alt pour l'accessibilité et la compréhension de l'image par l'assistant. |
Metafields
Utilisez les metafields pour les informations catalogue qui ne correspondent pas au schéma produit standard, par exemple les thèmes d'un voyage, sa durée, une matière, des conseils d'entretien ou un classement interne.
Chaque metafield contient quatre propriétés string obligatoires :
| Champ | Contraintes | Description |
|---|---|---|
namespace | 1 à 255 caractères ; lettres, chiffres, _, ., - | Regroupe les clés liées. Humind n'impose pas de convention métier. Utilisez un domaine stable comme travel, erp ou content. |
key | 1 à 255 caractères ; lettres, chiffres, _, ., - | Identifiant stable dans le namespace. Nous recommandons le snake_case. |
type | 1 à 255 caractères ; lettres, chiffres, _, ., - | Décrit comment interpréter la valeur, par exemple single_line_text_field, list.single_line_text_field ou html. |
value | String, maximum 100 000 caractères | Valeur stockée. Les listes doivent être encodées dans une chaîne JSON. |
La paire (namespace, key) doit être unique dans chaque tableau de metafields produit ou variante. Un produit ou une variante accepte au maximum 250 metafields.
{
"metafields": [
{
"namespace": "travel",
"key": "wish_category_titles",
"type": "list.single_line_text_field",
"value": "[\"Art et culture\",\"Voyage en famille\"]"
},
{
"namespace": "travel",
"key": "marketing_description_key_points",
"type": "html",
"value": "<ul><li>Explorer les champs de thé</li></ul>"
}
]
}Utilisez type: "html" lorsque la valeur contient du HTML. Humind le nettoie à l'entrée et retire les scripts, les gestionnaires d'évènements, les styles inline et les URL dangereuses. Les valeurs associées aux autres types sont stockées comme des chaînes opaques.
Les metafields sont acceptés par POST /products, POST /products/batch, PUT /products/{id}, PATCH /products/{id}, PUT /products/{id}/variants/{variant_external_id} et chaque ligne produit d'un import NDJSON. Ils sont également renvoyés par les réponses de lecture et d'écriture des produits.
Pour PATCH /products/{id}, omettre metafields conserve les metafields produit existants. Envoyer la propriété remplace tout le tableau de metafields produit, et envoyer [] le vide. Sur la sous-ressource variante, la propriété optionnelle metafields remplace également tout le tableau de metafields de cette variante.
Poussez les données utiles à l'IA au niveau produit, pas au niveau variante
Les metafields de variante sont stockés et renvoyés, mais l'assistant IA, les filtres de recherche et la configuration des metafields du dashboard ne lisent que les metafields au niveau produit. Tout ce que l'assistant doit connaître (composition, conseils d'utilisation, notes olfactives, …) doit être poussé sur le produit, même quand la donnée existe aussi sur les variantes.
Activez vos metafields pour l'IA
Pousser un metafield le stocke ; il n'alimente pas encore l'assistant. Chaque metafield doit être activé (inclusion IA, filtre de recherche, affichage) soit dans le dashboard, soit programmatiquement via PUT /settings/metafields, ce qui permet à une intégration entièrement automatisée de pousser la donnée et de l'activer dans le même pipeline.
Les metafields peuvent être traduits par langue : voir Translations.
Appartenances aux groupes de produits
Utilisez group_memberships pour rattacher le produit à une famille créée avec l'API Groupes de produits. Créez le groupe avant d'écrire les produits qui le référencent.
| Champ | Type | Requis | Description |
|---|---|---|---|
group_external_id | string | Oui | external_id d'un groupe de produits existant dans l'API publique. Unique dans le tableau d'appartenances du produit. |
selected_options | object[] | Non | Valeurs choisies pour ce produit. Chaque option ne peut apparaître qu'une fois. |
selected_options[].option_external_id | string | Oui | external_id d'une option définie sur le groupe référencé. |
selected_options[].value | string | Oui | Valeur de cette option pour ce produit, par exemple Bleu ou Velours. |
{
"group_memberships": [
{
"group_external_id": "canape-modele-1",
"selected_options": [
{ "option_external_id": "color", "value": "Bleu" },
{ "option_external_id": "material", "value": "Velours" }
]
}
]
}Omettre group_memberships conserve les appartenances existantes gérées par l'API publique. Envoyer [] les vide. Les appartenances gérées par Shopify ou un autre connecteur interne restent intactes dans les deux cas.
Les références inconnues renvoient 422 product_group_not_found ou 422 product_group_option_not_found. Le même champ et les mêmes règles s'appliquent aux endpoints produit synchrones et aux imports NDJSON.
Produits liés et complémentaires
Utilisez related_products et complementary_products pour donner à l'assistant des recommandations produit choisies par le marchand, avec la même sémantique que Shopify Search & Discovery :
related_products: des alternatives à ce produit (« vous aimerez aussi »).complementary_products: des produits qui vont bien avec celui-ci (« se marie bien avec »), que l'assistant suggère proactivement en cross-sell quand l'acheteur montre une intention d'achat.
Les deux champs sont des tableaux de références d'external_id produit, 25 entrées maximum chacun, uniques au sein du tableau :
{
"external_id": "eau-de-parfum-belle-50ml",
"related_products": ["eau-de-toilette-belle-100ml"],
"complementary_products": ["creme-corps-belle-200ml", "savon-belle"]
}Sémantique des références :
- Les références pointent vers des produits de votre propre catalogue par leur
external_id. L'ordre de création n'a pas d'importance : un id dont le produit n'a pas encore été poussé est stocké tel quel et commence à se résoudre dès que le produit référencé existe. - Les références vers des produits archivés, en brouillon, ou plus généralement non exposés par Humind ne se résolvent vers rien ; l'assistant ne les recommande jamais.
- Les auto-références sont ignorées silencieusement.
- Mêmes règles de mise à jour que les metafields : omettre la propriété conserve la liste stockée, envoyer la propriété remplace toute la liste,
[]la vide. Accepté par les endpoints produit synchrones et chaque ligne produit d'un import NDJSON.
Pour les marchands Shopify, rien à pousser : les recommandations configurées dans l'app Search & Discovery (produits complémentaires et liés) sont importées automatiquement à chaque sync catalogue.
Variant
| Champ | Type | Requis | Description |
|---|---|---|---|
external_id | string | Oui | Votre identifiant unique de variant (par exemple SKU). Unique au sein du produit. |
title | string | Non | Label du variant (par exemple "50 ml", "Black / Medium"). Optionnel : pour les produits mono-SKU vous pouvez l'omettre, le variant hérite alors du titre du produit. En pratique requis dès qu'un produit a plusieurs variants, sinon les acheteurs ne peuvent pas les distinguer. |
sku | string | Non | Stock-keeping unit. À afficher aux acheteurs quand pertinent. |
price | number | Oui | Prix de vente en décimal avec au plus 2 chiffres décimaux (par exemple 29.90). Doit être ≥ 0 et ≤ 1 000 000 000. Les valeurs comme 0.30000000000000004 (artefact de flottant JS de 0.1 + 0.2) sont rejetées avec validation_failed. |
compare_at_price | number | Non | Prix « avant » barré pour les promotions. Même format que price. Doit être strictement supérieur à price si défini : compare_at_price <= price renvoie validation_failed. |
currency | string | Oui | Code ISO 4217 (par exemple EUR, USD, GBP). Validé contre la liste ISO 4217 live : les codes inconnus sont rejetés avec validation_failed. |
available_for_sale | boolean | Non | Défaut true. |
inventory_quantity | integer | Non | Stock. Omettez pour marquer comme non tracké. |
metafields | object[] | Non | Métadonnées personnalisées de la variante. Même format et mêmes limites que les metafields produit. |
regional_pricing | object | Non | Map de codes pays ISO 3166-1 alpha-2 vers {currency, price, compare_at_price?} pour le pricing par région. Voir l'exemple ci-dessous. |
cart_action | object | Non | Ce qui se passe quand l'assistant propose un CTA « Add to cart » pour ce variant. Défaut { "type": "noop" } quand omis. Voir Cart action. |
Cart action
cart_action est une union discriminée : choisissez le type qui correspond à votre storefront.
type: 'redirect' : envoie l'acheteur vers une URL
| Champ | Type | Requis | Description |
|---|---|---|---|
type | 'redirect' | Oui | Envoie l'acheteur vers url. |
url | string | Oui | L'URL vers laquelle envoyer l'acheteur (typiquement online_store_url avec un query param de variant, mais n'importe quoi de joignable marche). |
target | 'product_page' | 'add_to_cart' | Non | Ce que fait l'ouverture de url, ce qui décide le libellé du CTA. product_page → le CTA affiche « Voir le produit » et y mène directement, puisque l'ajout se fait sur votre page. add_to_cart → url est un lien profond qui remplit le panier, et le CTA affiche « Ajouter au panier ». Absent vaut add_to_cart : les deux sont valides et indiscernables depuis l'URL, donc posez product_page explicitement plutôt que de laisser un CTA panier déposer l'acheteur sur une page sans rien avoir ajouté. Voir Ajout au panier. |
type: 'noop' : affiche le variant sans CTA
| Champ | Type | Requis | Description |
|---|---|---|---|
type | 'noop' | Oui | Remonte le variant dans le chat sans CTA actionnable (utile quand le checkout est gated, par exemple en B2B). |
type: 'prestashop' : add-to-cart AJAX natif PrestaShop
Pour les boutiques PrestaShop 1.7+ / 8, poussez les id_product et id_product_attribute natifs pour que le widget puisse POST directement sur votre endpoint /cart en same-origin (pas de CORS, pas de nouvel onglet, mise à jour inline du cart drawer natif).
| Champ | Type | Requis | Description |
|---|---|---|---|
type | 'prestashop' | Oui | Add-to-cart natif PrestaShop. |
id_product | integer | Oui | L'id_product PrestaShop. |
id_product_attribute | integer | Oui | L'id_product_attribute PrestaShop pour le variant. Utilisez 0 pour les produits sans combinations. |
product_url | string | Oui | L'URL de la page produit. Utilisé comme fallback de redirection quand window.prestashop n'est pas disponible sur la page du visiteur. |
type: 'shopify' : add-to-cart AJAX natif Shopify
Pour les boutiques Shopify, le widget POST sur l'endpoint /cart/add.js du storefront en same-origin. Les marchands sur l'app Shopify Humind n'ont jamais besoin de le pousser : l'app l'attache automatiquement à la synchronisation. Poussez-le explicitement uniquement pour les catalogues gérés hors de l'app.
| Champ | Type | Requis | Description |
|---|---|---|---|
type | 'shopify' | Oui | Add-to-cart natif Shopify. |
variant_id | integer | Oui | L'id numérique (legacy) du variant accepté par /cart/add.js. |
product_url | string | Non | URL de la page produit en fallback. Quand omis, le widget se rabat sur l'online_store_url du produit. |
properties | object | Non | Line-item properties attachées à la ligne du panier (ex. texte de gravure, tag de source). Valeurs string uniquement. |
type: 'woocommerce' : add-to-cart natif WooCommerce
Pour les boutiques WooCommerce, le widget soumet la requête standard add-to-cart vers la page produit en same-origin, puis rafraîchit les fragments panier du thème.
| Champ | Type | Requis | Description |
|---|---|---|---|
type | 'woocommerce' | Oui | Add-to-cart natif WooCommerce. |
product_id | integer | Oui | L'id du produit parent WooCommerce. |
variation_id | integer | Oui | L'id de la variation. Utilisez 0 pour les produits sans variations. |
product_url | string | Oui | L'URL de la page produit vers laquelle la requête add-to-cart est soumise. |
attributes | object | Non | Les valeurs attribute_* dont les produits variables ont besoin pour désambiguïser les variations sœurs (ex. { "pa_color": "red", "pa_size": "M" }). Valeurs string uniquement. |
type: 'function' : appeler la fonction d'ajout au panier de votre storefront
Pour les boutiques custom qui exposent une fonction JavaScript de quick-buy sur window (par exemple injectée via votre tag manager), le widget l'appelle directement pour que les visiteurs restent dans la conversation. Voir le guide Ajout au panier pour le contrat de la fonction.
| Champ | Type | Requis | Description |
|---|---|---|---|
type | 'function' | Oui | Appelle une fonction window exposée par le marchand. |
name | string | Oui | Le nom de la fonction sur window (ex. myQuickBuy). Doit être un identifiant JavaScript simple (lettres, chiffres, _, $, ne commençant pas par un chiffre, max 64 caractères) : le widget le lit comme une propriété de window et n'évalue jamais une expression. |
args | object | Non | Map plate de strings passée à la fonction comme unique argument, ex. { "productId": "SKU123" }. Clés max 64 caractères, valeurs max 256 caractères. |
product_url | string | Oui | Repli : quand la fonction n'est pas présente sur la page (visiteur hors d'un bucket d'AB test, page exclue, tag manager non chargé), le widget redirige le visiteur vers cette URL à la place. |
cart_url | string | Non | Votre page panier ou paiement. Si elle est renseignée, le toast de confirmation affiché après un ajout réussi propose un bouton « Aller au paiement » qui l'ouvre. Sans elle, le toast n'a pas de bouton : le widget ne peut pas savoir où se trouve votre panier. |
Auto-dérivation
Si votre company est configurée avec catalogIntegrationType: prestashop, shopify ou woocommerce (demandez-nous de marquer votre compte), vous pouvez omettre cart_action complètement sur chaque variant : l'API auto-dérive l'action native depuis vos champs external_id (produit) et external_id (variant). online_store_url est requis sur le produit pour les dérivations PrestaShop et WooCommerce. Poussez cart_action explicitement uniquement quand vous voulez override la dérivation pour des variants spécifiques (par exemple pour ajouter des attributes WooCommerce ou des properties Shopify, qui ne sont jamais auto-dérivés). Un cart_action explicite gagne toujours sur la dérivation.
Ces ids sont publics
Les ids produit et variant PrestaShop, Shopify et WooCommerce sont des identifiants publics du storefront : visibles dans le markup HTML, les URLs et les data attributes. Les pousser via l'API ne leak aucun secret.
Translations
translations est une map de codes de langue vers des overrides de champs par locale :
{
"default_language": "fr",
"title": "Crème hydratante",
"description": "Une crème légère pour le visage.",
"handle": "creme-hydratante",
"translations": {
"en": {
"title": "Hydrating cream",
"description": "A lightweight face cream.",
"handle": "hydrating-cream"
}
}
}Champs par locale que vous pouvez override :
| Champ | Type |
|---|---|
title | string |
description | string |
description_html | string |
handle | string |
online_store_url | string |
ingredients | string[] |
metafields | object[] |
Les clés non présentes dans translations retombent sur la valeur top-level (en default_language).
Les metafields traduits sont des overrides par langue appariés par (namespace, key) : quand l'assistant sert le produit dans une langue donnée, une entrée dans translations.<lang>.metafields remplace la value du metafield de base qui porte les mêmes namespace et key. Les metafields de base sans override gardent leur valeur en langue par défaut ; une entrée traduite sans contrepartie de base est stockée mais jamais exposée. Le tableau suit le même format et les mêmes limites que les metafields produit, et seule la liste de base pilote l'activation des metafields : aucune configuration par langue nécessaire.
{
"default_language": "fr",
"metafields": [
{ "namespace": "olfactif", "key": "notes_tete", "type": "list.single_line_text_field", "value": "[\"Bergamote\",\"Mandarine\"]" }
],
"translations": {
"en": {
"metafields": [
{ "namespace": "olfactif", "key": "notes_tete", "type": "list.single_line_text_field", "value": "[\"Bergamot\",\"Mandarin\"]" }
]
}
}
}Vous pouvez aussi patcher une locale à la fois sans renvoyer le produit complet via la sub-resource Translations dédiée.
HTML content
Plusieurs champs acceptent du HTML, notamment description_html, plus content_html sur les traductions. Humind sanitize le HTML côté serveur avant de le stocker. Le sanitizer applique une allowlist fixe :
- Tags préservés :
p, a, br, hr, em, strong, b, i, u, ul, ol, li, h1àh6,blockquote, pre, code, table, thead, tbody, tr, th, td, img, span, div. - Tags strippés :
script, iframe, style, object, embed, form, input, et tout autre tag non listé ci-dessus. - Attributs strippés : event handlers (
onclick,onerror, …),style, et toute URLjavascript:. - Attributs préservés :
hrefsur<a>(uniquementhttps,http,mailtoet URIsdata:valides),src,alt,width,heightsur<img>, plus un set limité de valeursclass.
Le HTML est sanitizé en entrée
Ne comptez pas sur l'API pour préserver votre HTML brut. Les tags comme <script> ou les attributs comme onclick sont strippés silencieusement, votre markup stocké peut être un sous-ensemble strict de ce que vous avez envoyé. Relisez la ressource après une écriture si vous voulez voir ce qui a été retenu.
Create or upsert
POST /products crée un nouveau produit, ou met à jour un existant si l'external_id existe déjà pour votre company.
Scope requis : catalog:write
Request
curl -X POST https://api.thehumind.com/public/v1/products \
-H "Authorization: Bearer hmd_live_..." \
-H "Idempotency-Key: 3f1a8d92-7c4b-4e6f-b2a1-d5e9c8f7a3b4" \
-H "Content-Type: application/json" \
-d '{
"external_id": "SKU-123",
"title": "Crème hydratante",
"handle": "creme-hydratante",
"description": "Une crème légère pour le visage.",
"status": "active",
"default_language": "fr",
"online_store_url": "https://example.com/products/creme-hydratante",
"brand": { "name": "Acme Skincare", "domain": "acme-skincare.com" },
"categories": ["Soin", "Hydratation"],
"images": [
{ "url": "https://cdn.example.com/creme.jpg", "alt": "Crème hydratante 50ml" }
],
"variants": [
{
"external_id": "SKU-123-50ML",
"title": "50 ml",
"sku": "SKU-123-50ML",
"price": 29.90,
"compare_at_price": 34.90,
"currency": "EUR",
"available_for_sale": true,
"inventory_quantity": 42,
"regional_pricing": {
"US": { "currency": "USD", "price": 32.00 }
},
"cart_action": {
"type": "redirect",
"url": "https://example.com/products/creme-hydratante?variant=50ml"
}
}
],
"translations": {
"en": {
"title": "Hydrating cream",
"description": "A lightweight face cream.",
"handle": "hydrating-cream"
}
}
}'Response 201 Created (nouveau produit) ou 200 OK (produit existant mis à jour)
{
"external_id": "SKU-123",
"humind_id": "65f1ab9c8e7d4a2b1c3d4e5f",
"title": "Crème hydratante",
"handle": "creme-hydratante",
"description": "Une crème légère pour le visage.",
"status": "active",
"default_language": "fr",
"online_store_url": "https://example.com/products/creme-hydratante",
"available_for_sale": true,
"brand": { "name": "Acme Skincare", "domain": "acme-skincare.com" },
"categories": ["Soin", "Hydratation"],
"images": [
{ "url": "https://cdn.example.com/creme.jpg", "alt": "Crème hydratante 50ml" }
],
"variants": [
{
"external_id": "SKU-123-50ML",
"title": "50 ml",
"sku": "SKU-123-50ML",
"price": 29.90,
"compare_at_price": 34.90,
"currency": "EUR",
"available_for_sale": true,
"inventory_quantity": 42,
"regional_pricing": {
"US": { "currency": "USD", "price": 32.00 }
},
"cart_action": {
"type": "redirect",
"url": "https://example.com/products/creme-hydratante?variant=50ml"
}
}
],
"translations": {
"en": {
"title": "Hydrating cream",
"description": "A lightweight face cream.",
"handle": "hydrating-cream"
}
},
"created_at": "2026-04-25T14:30:00Z",
"updated_at": "2026-04-25T14:30:00Z"
}200 vs 201
Un 201 Created veut dire que c'est un nouveau produit (première fois que l'API voit cet external_id). Un 200 OK veut dire que le produit existait déjà et a été mis à jour en place. Les deux sont des succès.
List products
GET /products renvoie les produits qui appartiennent à la company à laquelle la clé API est rattachée.
Scope requis : catalog:read
| Query parameter | Type | Description |
|---|---|---|
cursor | string | Cursor opaque renvoyé comme next_cursor à la page précédente. Omettez-le au premier appel. |
limit | integer | Items par page (1 à 100). Défaut 50. |
handle | string | Filtre sur un seul produit par match exact de handle. |
status | 'active' | 'draft' | 'archived' | 'unlisted' | 'all' | Filtre par statut. Sans ce paramètre, l'API renvoie les produits active, draft et unlisted. Utilisez archived pour les produits supprimés logiquement ou all pour désactiver le filtre de statut. |
Pagination par cursor
Les endpoints de liste renvoient jusqu'à limit items plus un next_cursor que vous pouvez passer pour récupérer la page suivante. Quand next_cursor est null, vous êtes à la fin. Le cursor est opaque : ne le parsez pas, le format peut changer dans une version future.
Request
curl https://api.thehumind.com/public/v1/products \
-H "Authorization: Bearer hmd_live_..."Pour filtrer par handle :
curl "https://api.thehumind.com/public/v1/products?handle=creme-hydratante" \
-H "Authorization: Bearer hmd_live_..."Response 200 OK
{
"data": [
{
"external_id": "SKU-123",
"humind_id": "65f1ab9c8e7d4a2b1c3d4e5f",
"title": "Crème hydratante",
"handle": "creme-hydratante",
"status": "active",
"default_language": "fr",
"variants": [
{
"external_id": "SKU-123-50ML",
"title": "50 ml",
"price": 29.90,
"currency": "EUR"
}
],
"created_at": "2026-04-25T14:30:00Z",
"updated_at": "2026-04-25T14:30:00Z"
}
]
}Retrieve a product
GET /products/{id} renvoie un seul produit. Le {id} accepte l'une ou l'autre forme documentée dans Identifiants.
Scope requis : catalog:read
Request, par external_id
curl https://api.thehumind.com/public/v1/products/api:SKU-123 \
-H "Authorization: Bearer hmd_live_..."Request, par humind_id
curl https://api.thehumind.com/public/v1/products/65f1ab9c8e7d4a2b1c3d4e5f \
-H "Authorization: Bearer hmd_live_..."Response 200 OK
Même forme que la réponse de création.
Replace a product
PUT /products/{id} fait un remplacement complet : le body de la requête devient le produit entier. Les champs que vous n'incluez pas sont reset à leurs défauts (ou unset là où c'est applicable).
À utiliser quand vous avez une représentation complète du produit de votre côté et voulez la mirorrer exactement. Pour des updates partiels, utilisez plutôt PATCH.
Scope requis : catalog:write
Request
curl -X PUT https://api.thehumind.com/public/v1/products/api:SKU-123 \
-H "Authorization: Bearer hmd_live_..." \
-H "Idempotency-Key: 7a2b3c4d-5e6f-4a8b-9c0d-1e2f3a4b5c6d" \
-H "Content-Type: application/json" \
-d '{
"external_id": "SKU-123",
"title": "Crème hydratante (édition limitée)",
"handle": "creme-hydratante",
"status": "active",
"default_language": "fr",
"variants": [
{ "external_id": "SKU-123-50ML", "title": "50 ml", "price": 32.00, "currency": "EUR" }
]
}'Response 200 OK
Renvoie l'objet produit complet, même forme que la réponse de création.
Replace est destructif
PUT enlève tout champ, variant, image ou traduction non présent dans le body. Si vous voulez juste bumper le prix d'un variant, utilisez plutôt PATCH.
Update a product
PATCH /products/{id} fait un update partiel : seuls les champs présents dans le body sont modifiés. Le reste reste comme avant.
Scope requis : catalog:write
Request, bumper le prix d'un seul variant
curl -X PATCH https://api.thehumind.com/public/v1/products/api:SKU-123 \
-H "Authorization: Bearer hmd_live_..." \
-H "Idempotency-Key: 9b8a7c6d-5e4f-4a3b-2c1d-0e9f8a7b6c5d" \
-H "Content-Type: application/json" \
-d '{
"variants": [
{ "external_id": "SKU-123-50ML", "price": 32.00 }
]
}'Quand vous envoyez un array variants en PATCH, chaque entrée est matchée sur external_id et mergée avec le variant existant du même external ID. Les variants que vous ne listez pas sont laissés intouchés. Pour supprimer un variant, utilisez PUT (full replace) sans ce variant dans l'array.
Response 200 OK
Renvoie l'objet produit complet mis à jour.
Archiver (ou supprimer) un produit
DELETE /products/{id} fait un soft delete par défaut : le status du produit passe à archived, available_for_sale bascule à false, et l'assistant arrête de proposer le produit. Le record reste en base, donc vous pouvez le restaurer plus tard en PATCH-ant le statut à active.
Passez ?force=true pour faire un hard delete : le document est supprimé physiquement de la base après la cascade d'archivage. Il n'y a pas d'un-delete ; à n'utiliser que quand le SKU doit vraiment disparaître (ex. nettoyage d'un catalogue de test, SKU créé par erreur).
Scope requis : catalog:write
Query parameters
| Nom | Type | Requis | Description |
|---|---|---|---|
force | boolean | Non | Valeurs truthy (insensibles à la casse) : true, 1, yes, on. Valeurs falsy : false, 0, no, off, ou omis. Toute autre valeur renvoie 400 validation_failed. Quand truthy, supprime définitivement le produit. Défaut soft delete. |
Request : soft delete (par défaut)
curl -X DELETE https://api.thehumind.com/public/v1/products/api:SKU-123 \
-H "Authorization: Bearer hmd_live_..." \
-H "Idempotency-Key: 1a2b3c4d-5e6f-4789-9abc-def012345678"Request : hard delete
curl -X DELETE 'https://api.thehumind.com/public/v1/products/api:SKU-123?force=true' \
-H "Authorization: Bearer hmd_live_..." \
-H "Idempotency-Key: 1a2b3c4d-5e6f-4789-9abc-def012345678"Response 204 No Content
Pas de body.
Soft vs. hard delete
Le soft delete est recommandé pour les opérations marchandes normales : les conversations qui référençaient le produit restent cohérentes, vous pouvez désarchiver en un PATCH, et les caches downstream (vector index, dashboards) se nettoient via la cascade d'archivage.
Le hard delete (?force=true) sert au nettoyage de catalogue : typiquement des produits de test qu'on veut totalement enlevés. Le soft delete déclenche d'abord la routine d'archivage ; ?force=true supprime ensuite définitivement la ressource. Les collections qui référençaient le produit sautent simplement la référence absente ; on ne cascade pas plus loin.
Batch upsert
POST /products/batch accepte jusqu'à 500 produits dans un seul appel et renvoie une réponse 207 Multi-Status avec une entrée par item. À utiliser pour le backfill initial d'un catalogue ou n'importe quelle mise à jour bulk, c'est largement plus rapide que 500 POST séquentiels.
Scope requis : catalog:write
| Contrainte | Valeur |
|---|---|
| Max items par batch | 500 |
| Max taille body | 5 MB |
| Comportement | Chaque item est traité indépendamment ; un échec ne bloque pas les autres. |
Request
curl -X POST https://api.thehumind.com/public/v1/products/batch \
-H "Authorization: Bearer hmd_live_..." \
-H "Idempotency-Key: 4d3c2b1a-9f8e-4d7c-6b5a-4f3e2d1c0b9a" \
-H "Content-Type: application/json" \
-d '{
"items": [
{
"external_id": "SKU-123",
"title": "Crème hydratante",
"handle": "creme-hydratante",
"default_language": "fr",
"variants": [
{ "external_id": "SKU-123-50ML", "title": "50 ml", "price": 29.90, "currency": "EUR" }
]
},
{
"external_id": "SKU-456",
"title": "Sérum éclat",
"handle": "serum-eclat",
"default_language": "fr",
"variants": [
{ "external_id": "SKU-456-30ML", "title": "30 ml", "price": 49.00, "currency": "EUR" }
]
},
{
"external_id": "SKU-789",
"title": "Bad data",
"default_language": "fr"
}
]
}'Response 207 Multi-Status
{
"results": [
{
"external_id": "SKU-123",
"status": "created",
"humind_id": "65f1ab9c8e7d4a2b1c3d4e5f"
},
{
"external_id": "SKU-456",
"status": "updated",
"humind_id": "65f1cd9e8e7d4a2b1c3d4e60"
},
{
"external_id": "SKU-789",
"status": "failed",
"error": {
"code": "validation_failed",
"message": "Invalid product payload.",
"details": {
"issues": [
{ "path": ["handle"], "message": "Required", "code": "invalid_type" }
]
}
}
}
]
}| Champ | Type | Description |
|---|---|---|
results[i].external_id | string | L'external_id de l'item d'entrée, écho pour matching. |
results[i].status | string | Une valeur parmi created, updated, failed. |
results[i].humind_id | string | Présent en cas de succès, l'id Humind 24-hex assigné. |
results[i].error | object | Présent en cas d'échec. Même forme qu'un objet d'erreur top-level. |
Doublons d'external_id au sein d'un batch
Si le même external_id apparaît plusieurs fois dans items, la première occurrence est traitée et les doublons suivants reviennent avec status: "failed" et le code duplicate_external_id_in_batch. Dédupliquez côté client avant l'envoi.
Wrapper ou tableau bare
Le body peut être un tableau JSON comme montré ci-dessus, ou wrappé : { "items": [ ... ] }. L'API normalise les deux vers la même réponse results.
Idempotency sur les batches
L'Idempotency-Key s'applique au batch entier. Un retry du même batch avec la même clé rejoue toute la réponse 207 : aucun item n'est traité deux fois. Générez un nouveau UUID pour un autre batch.
Translations sub-resource
Pour les produits qui existent déjà, vous pouvez patcher une seule locale sans renvoyer le payload complet. Utilisez le champ translations embarqué sur POST / PUT / PATCH pour le seed initial, puis pilotez les updates suivants via ces endpoints.
{lang} est un tag court BCP 47, en, fr, de, pt-BR, zh-CN, etc. La regex de validation est ^[a-z]{2}(-[A-Z]{2})?$. Une valeur invalide renvoie 422 avec code: invalid_lang_format.
Set or update a translation
PUT /products/{id}/translations/{lang} upserte la traduction pour une seule locale. Les champs que vous omettez sont laissés intouchés sur un update ; à la première écriture, ils défaulent à unset.
Scope requis : catalog:write
| Champ | Type | Description |
|---|---|---|
title | string | Titre d'affichage localisé. |
description | string | Description texte brut localisée. |
description_html | string | Description HTML localisée. Sanitizée, voir HTML content. |
handle | string | Slug URL-safe localisé. |
online_store_url | string | URL publique localisée sur votre storefront. |
ingredients | string[] | Liste d'ingrédients localisée. |
metafields | object[] | Overrides de metafields par langue, appariés par (namespace, key). Même format et même sémantique que les metafields traduits. Envoyer la propriété remplace toute la liste de la langue ; [] la vide. |
Tous les champs sont optionnels individuellement, mais le body doit contenir au moins un. Un body vide renvoie 400 validation_failed.
Request
curl -X PUT https://api.thehumind.com/public/v1/products/api:SKU-123/translations/en \
-H "Authorization: Bearer hmd_live_..." \
-H "Idempotency-Key: 5f6a7b8c-9d0e-4f1a-2b3c-4d5e6f7a8b9c" \
-H "Content-Type: application/json" \
-d '{
"title": "Hydrating cream",
"description": "A lightweight face cream.",
"handle": "hydrating-cream",
"online_store_url": "https://example.com/en/products/hydrating-cream"
}'Response 200 OK
Renvoie l'objet produit complet, même forme que la réponse de création, pour que vous voyiez toutes les locales actuellement stockées, pas juste celle que vous avez écrite.
Remove a translation
DELETE /products/{id}/translations/{lang} enlève la traduction pour une seule locale. Idempotent : supprimer une locale qui n'existe pas est un no-op, pas une erreur.
Scope requis : catalog:write
Request
curl -X DELETE https://api.thehumind.com/public/v1/products/api:SKU-123/translations/en \
-H "Authorization: Bearer hmd_live_..." \
-H "Idempotency-Key: 6a7b8c9d-0e1f-4a2b-3c4d-5e6f7a8b9c0d"Response 204 No Content
Pas de body.
Variants sub-resource
Pour les produits qui existent déjà, vous pouvez gérer un seul variant sans renvoyer le produit complet. Le variant est identifié par son external_id dans le path de l'URL.
{variant_external_id} est le même identifiant que celui que vous mettez sur le champ external_id du variant, typiquement votre SKU.
Set or update a variant
PUT /products/{id}/variants/{variant_external_id} upserte un seul variant. Le body match le schéma Variant, sans external_id : cette valeur est prise depuis l'URL.
Scope requis : catalog:write
Request
curl -X PUT https://api.thehumind.com/public/v1/products/api:SKU-123/variants/SKU-123-50ML \
-H "Authorization: Bearer hmd_live_..." \
-H "Idempotency-Key: 7b8c9d0e-1f2a-4b3c-4d5e-6f7a8b9c0d1e" \
-H "Content-Type: application/json" \
-d '{
"title": "50 ml",
"sku": "SKU-123-50ML",
"price": 29.90,
"compare_at_price": 34.90,
"currency": "EUR",
"available_for_sale": true,
"inventory_quantity": 42
}'Response 200 OK
Renvoie l'objet produit complet pour que vous voyiez le variant mis à jour à côté du reste du produit.
Remove a variant
DELETE /products/{id}/variants/{variant_external_id} enlève un seul variant du produit. Idempotent : supprimer un variant qui n'existe pas est un no-op, pas une erreur.
Scope requis : catalog:write
Un produit a besoin d'au moins un variant
Vous ne pouvez pas enlever le dernier variant d'un produit, ça le laisserait sans prix ni currency. Pour retirer un produit complètement, archivez-le plutôt.
Request
curl -X DELETE https://api.thehumind.com/public/v1/products/api:SKU-123/variants/SKU-123-50ML \
-H "Authorization: Bearer hmd_live_..." \
-H "Idempotency-Key: 8c9d0e1f-2a3b-4c4d-5e6f-7a8b9c0d1e2f"Response 204 No Content
Pas de body.
Erreurs courantes
Les endpoints products peuvent renvoyer n'importe lequel des statuts HTTP standards, mais voici ceux que vous verrez le plus souvent :
| Statut | Code | Quand | Fix |
|---|---|---|---|
401 | missing_credentials, invalid_key_format, invalid_key, revoked | Le header d'auth manque, est malformé, ou la clé n'est plus active. | Voir Authentication. |
403 | insufficient_scope | La clé n'a pas catalog:read (pour GET) ou catalog:write (pour POST/PUT/PATCH/DELETE). | Créez une nouvelle clé avec le bon scope. |
404 | not_found | Le {id} ne correspond pas à un produit qui appartient à votre company. | Vérifiez l'external_id ou le humind_id. Les lookups cross-tenant renvoient aussi 404. |
404 | variant_not_found | Le {variant_external_id} sur un appel à la sub-resource variants ne correspond à aucun variant de ce produit. | Vérifiez l'external_id du variant. Le DELETE est idempotent, voir les notes là-bas avant de retenter. |
409 | idempotency_conflict | Même Idempotency-Key réutilisée avec un body différent. | Générez un UUID frais. |
400 | validation_failed | Le body a échoué le schéma Product. details pointe sur le mauvais champ. | Corrigez le champ et renvoyez. |
422 | invalid_lang_format | Le segment {lang} d'une URL de sub-resource translations ne match pas ^[a-z]{2}(-[A-Z]{2})?$. | Utilisez un tag court BCP 47 comme en, fr, pt-BR. |
Pour aller plus loin
- Authentication : générer une clé
catalog:write. - Conventions : identifiants, idempotency, dates.
- Errors : référence complète des codes d'erreur.