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' | Non | Défaut active. archived cache le produit à l'assistant. draft est pour stager les produits non publiés. |
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 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. |
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). |
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. |
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é. |
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). |
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. |
Auto-dérivation
Si votre company est configurée avec catalogIntegrationType: prestashop, vous pouvez omettre cart_action complètement sur chaque variant — l'API auto-dérive l'action prestashop depuis vos champs external_id (produit) et external_id (variant). Poussez-le explicitement uniquement quand vous voulez override la dérivation pour des variants spécifiques.
Pourquoi PrestaShop est autorisé et pas Shopify/WooCommerce
Les id_product et id_product_attribute PrestaShop sont publics — visibles dans le markup HTML, les URLs et les data attributes. Les pousser via l'API ne leak aucun secret. À l'inverse, les GIDs Shopify et les variation IDs WooCommerce sont privés à la plateforme et ne sont jamais set que par nos adaptateurs internes, jamais acceptés sur la surface publique.
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[] |
Les clés non présentes dans translations retombent sur la valeur top-level (en default_language).
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' | Filtre par status. Les produits soft-deleted vivent sous archived. |
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. |
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.