Skip to content

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

ChampTypeRequisDescription
external_idstringOui (à 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_idstringRenvoyé seulement24 caractères hex ObjectId qu'Humind assigne. Stable pendant toute la vie du produit.
titlestringOuiTitre d'affichage dans default_language.
descriptionstringNonDescription en texte brut.
description_htmlstringNonDescription HTML. Sanitizée côté serveur, voir HTML content.
handlestringNonSlug 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'NonDé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'NonDé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_urlstringNonURL publique du produit sur votre storefront. L'assistant l'utilise pour deep-linker les acheteurs depuis le chat.
default_languagestringNonTag 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_salebooleanRenvoyé seulementCalculé 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).
brandobjectNonVoir Brand.
categoriesstring[]NonNoms de catégories free-form. Utilisé pour un soft-grouping ; n'a pas à correspondre aux collections Humind.
imagesobject[]NonVoir Image. La première image est la principale dans le chat.
metafieldsobject[]NonMétadonnées produit personnalisées. Voir Metafields. Maximum 250 entrées.
group_membershipsobject[]NonFamilles de produits auxquelles ce produit appartient. Voir Appartenances aux groupes de produits. Maximum 20 entrées.
variantsobject[]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.
translationsobjectNonMap de codes ISO 639-1 vers les overrides par locale. Voir Translations.
created_atISO 8601Renvoyé seulementTimestamp de création (UTC).
updated_atISO 8601Renvoyé seulementTimestamp 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

ChampTypeRequisDescription
namestringOuiNom d'affichage de la marque.
domainstringNonDomaine web canonique de la marque (sans protocole). Utilisé pour la déduplication et le contexte assistant.

Image

ChampTypeRequisDescription
urlstringOuiURL HTTPS publiquement accessible. Humind récupère et cache l'image ; assurez-vous que l'URL est stable.
altstringNonTexte 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 :

ChampContraintesDescription
namespace1 à 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.
key1 à 255 caractères ; lettres, chiffres, _, ., -Identifiant stable dans le namespace. Nous recommandons le snake_case.
type1 à 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.
valueString, maximum 100 000 caractèresValeur 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.

json
{
  "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.

ChampTypeRequisDescription
group_external_idstringOuiexternal_id d'un groupe de produits existant dans l'API publique. Unique dans le tableau d'appartenances du produit.
selected_optionsobject[]NonValeurs choisies pour ce produit. Chaque option ne peut apparaître qu'une fois.
selected_options[].option_external_idstringOuiexternal_id d'une option définie sur le groupe référencé.
selected_options[].valuestringOuiValeur de cette option pour ce produit, par exemple Bleu ou Velours.
json
{
  "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 :

json
{
  "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

ChampTypeRequisDescription
external_idstringOuiVotre identifiant unique de variant (par exemple SKU). Unique au sein du produit.
titlestringNonLabel 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.
skustringNonStock-keeping unit. À afficher aux acheteurs quand pertinent.
pricenumberOuiPrix 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_pricenumberNonPrix « 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.
currencystringOuiCode 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_salebooleanNonDéfaut true.
inventory_quantityintegerNonStock. Omettez pour marquer comme non tracké.
metafieldsobject[]NonMétadonnées personnalisées de la variante. Même format et mêmes limites que les metafields produit.
regional_pricingobjectNonMap 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_actionobjectNonCe 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

ChampTypeRequisDescription
type'redirect'OuiEnvoie l'acheteur vers url.
urlstringOuiL'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'NonCe 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_carturl 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

ChampTypeRequisDescription
type'noop'OuiRemonte 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).

ChampTypeRequisDescription
type'prestashop'OuiAdd-to-cart natif PrestaShop.
id_productintegerOuiL'id_product PrestaShop.
id_product_attributeintegerOuiL'id_product_attribute PrestaShop pour le variant. Utilisez 0 pour les produits sans combinations.
product_urlstringOuiL'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.

ChampTypeRequisDescription
type'shopify'OuiAdd-to-cart natif Shopify.
variant_idintegerOuiL'id numérique (legacy) du variant accepté par /cart/add.js.
product_urlstringNonURL de la page produit en fallback. Quand omis, le widget se rabat sur l'online_store_url du produit.
propertiesobjectNonLine-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.

ChampTypeRequisDescription
type'woocommerce'OuiAdd-to-cart natif WooCommerce.
product_idintegerOuiL'id du produit parent WooCommerce.
variation_idintegerOuiL'id de la variation. Utilisez 0 pour les produits sans variations.
product_urlstringOuiL'URL de la page produit vers laquelle la requête add-to-cart est soumise.
attributesobjectNonLes 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.

ChampTypeRequisDescription
type'function'OuiAppelle une fonction window exposée par le marchand.
namestringOuiLe 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.
argsobjectNonMap 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_urlstringOuiRepli : 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_urlstringNonVotre 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 :

json
{
  "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 :

ChampType
titlestring
descriptionstring
description_htmlstring
handlestring
online_store_urlstring
ingredientsstring[]
metafieldsobject[]

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.

json
{
  "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 URL javascript:.
  • Attributs préservés : href sur <a> (uniquement https, http, mailto et URIs data: valides), src, alt, width, height sur <img>, plus un set limité de valeurs class.

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

bash
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)

json
{
  "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 parameterTypeDescription
cursorstringCursor opaque renvoyé comme next_cursor à la page précédente. Omettez-le au premier appel.
limitintegerItems par page (1 à 100). Défaut 50.
handlestringFiltre 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

bash
curl https://api.thehumind.com/public/v1/products \
  -H "Authorization: Bearer hmd_live_..."

Pour filtrer par handle :

bash
curl "https://api.thehumind.com/public/v1/products?handle=creme-hydratante" \
  -H "Authorization: Bearer hmd_live_..."

Response 200 OK

json
{
  "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

bash
curl https://api.thehumind.com/public/v1/products/api:SKU-123 \
  -H "Authorization: Bearer hmd_live_..."

Request, par humind_id

bash
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

bash
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

bash
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

NomTypeRequisDescription
forcebooleanNonValeurs 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)

bash
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

bash
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

ContrainteValeur
Max items par batch500
Max taille body5 MB
ComportementChaque item est traité indépendamment ; un échec ne bloque pas les autres.

Request

bash
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

json
{
  "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" }
          ]
        }
      }
    }
  ]
}
ChampTypeDescription
results[i].external_idstringL'external_id de l'item d'entrée, écho pour matching.
results[i].statusstringUne valeur parmi created, updated, failed.
results[i].humind_idstringPrésent en cas de succès, l'id Humind 24-hex assigné.
results[i].errorobjectPré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

ChampTypeDescription
titlestringTitre d'affichage localisé.
descriptionstringDescription texte brut localisée.
description_htmlstringDescription HTML localisée. Sanitizée, voir HTML content.
handlestringSlug URL-safe localisé.
online_store_urlstringURL publique localisée sur votre storefront.
ingredientsstring[]Liste d'ingrédients localisée.
metafieldsobject[]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

bash
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

bash
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

bash
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

bash
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 :

StatutCodeQuandFix
401missing_credentials, invalid_key_format, invalid_key, revokedLe header d'auth manque, est malformé, ou la clé n'est plus active.Voir Authentication.
403insufficient_scopeLa 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.
404not_foundLe {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.
404variant_not_foundLe {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.
409idempotency_conflictMême Idempotency-Key réutilisée avec un body différent.Générez un UUID frais.
400validation_failedLe body a échoué le schéma Product. details pointe sur le mauvais champ.Corrigez le champ et renvoyez.
422invalid_lang_formatLe 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

Released under the proprietary Humind license.