Collections
Les collections sont des groupes nommés de produits. L'assistant IA les utilise pour affiner ses recommandations à un sous-ensemble de votre catalogue, « montre-moi les bestsellers », « qu'est-ce qui est dans la collection été 2026 », « quelque chose dans le gift guide ». Poussez votre structure de merchandising une fois et l'assistant la respectera partout où il fait remonter des produits.
Les endpoints ici couvrent create, read, update, replace, archive et batch sur les collections, y compris leurs memberships produits et traductions embarquées.
L'objet Collection
| Champ | Type | Requis | Description |
|---|---|---|---|
external_id | string | Oui (à la création) | Votre identifiant unique pour cette collection. Free-form, doit être unique par company. Utilisé comme clé d'upsert : reposter avec la même valeur met à jour la collection existante. |
humind_id | string | Renvoyé seulement | 24 caractères hex ObjectId qu'Humind assigne. Stable pendant toute la vie de la collection. |
title | string | Oui | Titre d'affichage dans default_language. |
handle | string | Non | Slug URL-safe, minuscule, séparé par tirets. Unique par company quand défini — assigner un handle qui appartient déjà à une autre collection (matched sur un autre external_id) renvoie 409 handle_already_used. Les upserts sur le même external_id gardent leur handle existant. |
description | string | Non | Description en texte brut. |
description_html | string | Non | Description HTML. Sanitizée côté serveur, voir HTML content. |
status | 'active' | 'archived' | Non | Défaut active. archived cache la collection à l'assistant. |
default_language | string | Non | Code ISO 639-1 de la langue des champs top-level. |
source | 'public-api' | 'shopify' | 'supersmart' | Non | Défaut public-api. Tagge la collection avec l'intégration qui possède cette donnée — utile quand vous poussez via cette API des données que vous maintenez aussi dans un autre système. La même valeur sert de clé de lookup pour l'upsert, donc re-poster avec le même source + external_id met à jour la même row. Toute autre valeur est rejetée avec une erreur 400 validation_failed sur source. |
products | object[] | Non | Memberships produits. Voir Lier les produits par external_id. |
translations | object[] | Non | 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). |
Membership produit
Chaque entrée dans products décrit un produit qui appartient à la collection.
| Champ | Type | Requis | Description |
|---|---|---|---|
external_id | string | Oui | L'external_id d'un produit existant. Le serveur le résout en humind_id correspondant au moment de l'écriture. Voir Lier les produits par external_id. |
include_all_variants | boolean | Non | Défaut true. Quand true, tous les variants du produit font partie de la collection. |
included_variants | string[] | Non | Requis si include_all_variants vaut false. Liste des external_id de variants à inclure. Les variants non listés sont exclus de la collection. |
Translations
translations est une liste d'overrides par locale, keyée par language :
{
"default_language": "fr",
"title": "Nos meilleures ventes",
"description": "Les produits les plus aimés de la saison.",
"translations": [
{
"language": "en",
"title": "Bestsellers",
"description": "Our most-loved products this season."
}
]
}Champs par locale que vous pouvez override :
| Champ | Type |
|---|---|
title | string |
description | string |
description_html | string |
Les locales non listées dans translations retombent sur la valeur top-level (en default_language).
Vous pouvez aussi patcher une locale à la fois sans renvoyer la collection complète via la sub-resource Translations dédiée.
HTML content
Le champ description_html accepte du HTML. Humind sanitize le HTML côté serveur avant de le stocker, avec 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 /collections crée une nouvelle collection, ou met à jour une existante si l'external_id existe déjà pour votre company.
Scope requis : catalog:write
Request
curl -X POST https://api.thehumind.com/public/v1/collections \
-H "Authorization: Bearer hmd_live_..." \
-H "Idempotency-Key: 3f1a8d92-7c4b-4e6f-b2a1-d5e9c8f7a3b4" \
-H "Content-Type: application/json" \
-d '{
"external_id": "col-bestsellers",
"handle": "bestsellers",
"title": "Bestsellers",
"description": "Our most-loved products this season.",
"status": "active",
"default_language": "en",
"products": [
{ "external_id": "SKU-123", "include_all_variants": true },
{ "external_id": "SKU-456", "include_all_variants": false, "included_variants": ["SKU-456-A"] }
],
"translations": [
{
"language": "fr",
"title": "Nos meilleures ventes",
"description": "Les produits les plus aimés de la saison."
}
]
}'Response 201 Created (nouvelle collection) ou 200 OK (collection existante mise à jour)
{
"external_id": "col-bestsellers",
"humind_id": "65f1ab9c8e7d4a2b1c3d4e5f",
"handle": "bestsellers",
"title": "Bestsellers",
"description": "Our most-loved products this season.",
"status": "active",
"default_language": "en",
"products": [
{ "external_id": "SKU-123", "include_all_variants": true },
{ "external_id": "SKU-456", "include_all_variants": false, "included_variants": ["SKU-456-A"] }
],
"translations": [
{
"language": "fr",
"title": "Nos meilleures ventes",
"description": "Les produits les plus aimés de la saison."
}
],
"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 une nouvelle collection (première fois que l'API voit cet external_id). Un 200 OK veut dire que la collection existait déjà et a été mise à jour en place. Les deux sont des succès.
List collections
GET /collections renvoie les collections 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 une seule collection par match exact de handle. |
Request
curl https://api.thehumind.com/public/v1/collections?limit=50 \
-H "Authorization: Bearer hmd_live_..."Pour récupérer la page suivante :
curl "https://api.thehumind.com/public/v1/collections?limit=50&cursor=eyJpZCI6IjY1ZjEuLi4ifQ" \
-H "Authorization: Bearer hmd_live_..."Response 200 OK
{
"data": [
{
"external_id": "col-bestsellers",
"humind_id": "65f1ab9c8e7d4a2b1c3d4e5f",
"handle": "bestsellers",
"title": "Bestsellers",
"status": "active",
"default_language": "en",
"products": [
{ "external_id": "SKU-123", "include_all_variants": true }
],
"created_at": "2026-04-25T14:30:00Z",
"updated_at": "2026-04-25T14:30:00Z"
}
],
"next_cursor": "eyJpZCI6IjY1ZjFjZDllOGU3ZDRhMmIxYzNkNGU2MCJ9"
}Quand next_cursor vaut null ou est absent, vous êtes à la dernière page.
Retrieve a collection
GET /collections/{id} renvoie une seule collection. 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/collections/api:col-bestsellers \
-H "Authorization: Bearer hmd_live_..."Request, par humind_id
curl https://api.thehumind.com/public/v1/collections/65f1ab9c8e7d4a2b1c3d4e5f \
-H "Authorization: Bearer hmd_live_..."Response 200 OK
Même forme que la réponse de création.
Replace a collection
PUT /collections/{id} fait un remplacement complet : le body de la requête devient la collection entière. Les champs que vous n'incluez pas sont reset à leurs défauts (ou unset là où c'est applicable). L'array products que vous envoyez remplace le membership courant en entier.
À utiliser quand vous avez une représentation complète de la collection 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/collections/api:col-bestsellers \
-H "Authorization: Bearer hmd_live_..." \
-H "Idempotency-Key: 7a2b3c4d-5e6f-4a8b-9c0d-1e2f3a4b5c6d" \
-H "Content-Type: application/json" \
-d '{
"external_id": "col-bestsellers",
"title": "Bestsellers, April 2026",
"handle": "bestsellers",
"status": "active",
"default_language": "en",
"products": [
{ "external_id": "SKU-123", "include_all_variants": true },
{ "external_id": "SKU-789", "include_all_variants": true }
]
}'Response 200 OK
Renvoie l'objet collection complet, même forme que la réponse de création.
Replace est destructif
PUT enlève tout champ, membership produit ou traduction non présent dans le body. Pour ajouter ou enlever un seul produit, utilisez plutôt PATCH.
Update a collection
PATCH /collections/{id} fait un update partiel : seuls les champs présents dans le body sont modifiés. Le reste reste comme avant.
Quand vous envoyez un array products en PATCH, il remplace la liste de membership en entier, il n'y a pas de merge par entrée. Pour ajouter un produit, envoyez la liste existante plus la nouvelle entrée. Pour en enlever un, envoyez la liste existante moins cette entrée.
Scope requis : catalog:write
Request, renommer sans toucher au membership
curl -X PATCH https://api.thehumind.com/public/v1/collections/api:col-bestsellers \
-H "Authorization: Bearer hmd_live_..." \
-H "Idempotency-Key: 9b8a7c6d-5e4f-4a3b-2c1d-0e9f8a7b6c5d" \
-H "Content-Type: application/json" \
-d '{
"title": "Bestsellers, April 2026"
}'Request, ajouter un produit à la collection
curl -X PATCH https://api.thehumind.com/public/v1/collections/api:col-bestsellers \
-H "Authorization: Bearer hmd_live_..." \
-H "Idempotency-Key: 2c3d4e5f-6789-4abc-9def-0123456789ab" \
-H "Content-Type: application/json" \
-d '{
"products": [
{ "external_id": "SKU-123", "include_all_variants": true },
{ "external_id": "SKU-456", "include_all_variants": false, "included_variants": ["SKU-456-A"] },
{ "external_id": "SKU-999", "include_all_variants": true }
]
}'Response 200 OK
Renvoie l'objet collection complet mis à jour.
Archiver (ou supprimer) une collection
DELETE /collections/{id} fait un soft delete par défaut : le status de la collection passe à archived et l'assistant arrête de l'utiliser pour ses recommandations. 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 la collection doit vraiment disparaître (ex. nettoyage d'un catalogue de test, collection créée 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 la collection. Défaut soft delete. |
Request — soft delete (par défaut)
curl -X DELETE https://api.thehumind.com/public/v1/collections/api:bestsellers \
-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/collections/api:bestsellers?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 produits qui sont dans la collection restent intacts, vous pouvez désarchiver en un PATCH, et l'historique de chat qui ancrait ses recommandations sur cette collection reste cohérent.
Le hard delete (?force=true) sert au nettoyage de catalogue — typiquement une collection de test qu'on veut totalement enlevée. Le soft delete déclenche d'abord la routine d'archivage ; ?force=true supprime ensuite définitivement la ressource. Les produits qui étaient groupés par cette collection ne sont pas affectés ; seul le regroupement lui-même disparaît.
Batch upsert
POST /collections/batch accepte jusqu'à 500 collections dans un seul appel et renvoie une réponse 207 Multi-Status avec une entrée par item. À utiliser pour le sync initial de votre structure de merchandising ou n'importe quelle mise à jour bulk.
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/collections/batch \
-H "Authorization: Bearer hmd_live_..." \
-H "Idempotency-Key: 4d3c2b1a-9f8e-4d7c-6b5a-4f3e2d1c0b9a" \
-H "Content-Type: application/json" \
-d '{
"items": [
{
"external_id": "col-bestsellers",
"title": "Bestsellers",
"default_language": "en",
"products": [
{ "external_id": "SKU-123", "include_all_variants": true }
]
},
{
"external_id": "col-new-arrivals",
"title": "New arrivals",
"default_language": "en",
"products": [
{ "external_id": "SKU-456", "include_all_variants": true }
]
},
{
"external_id": "col-bad",
"default_language": "en"
}
]
}'Response 207 Multi-Status
{
"results": [
{
"external_id": "col-bestsellers",
"status": "created",
"humind_id": "65f1ab9c8e7d4a2b1c3d4e5f"
},
{
"external_id": "col-new-arrivals",
"status": "updated",
"humind_id": "65f1cd9e8e7d4a2b1c3d4e60",
"unresolved_products": ["SKU-456"]
},
{
"external_id": "col-bad",
"status": "failed",
"error": {
"code": "validation_failed",
"message": "Invalid collection payload.",
"details": {
"issues": [
{ "path": ["title"], "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].unresolved_products | string[] | Optionnel. Liste les external_id produits qui ne correspondent à aucun produit qui appartient à votre company au moment de l'écriture. Pas une erreur. Voir ci-dessous. |
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.
Linking products by external_id
Quand vous poussez une collection, chaque entrée dans products référence un produit par votre external_id, pas par humind_id. Le serveur résout la référence au moment de l'écriture :
- Si l'
external_idcorrespond à un produit qui appartient à votre company, le membership est enregistré avec lehumind_idcorrespondant en interne. - Si l'
external_idne correspond à aucun produit, le membership est quand même enregistré, mais l'external_idnon matché est rapporté dansunresolved_productsdans la réponse. C'est un warning, pas une erreur : la requête réussit.
La collection lie automatiquement les produits non résolus dès que vous les poussez. Vous n'avez pas besoin de re-PATCH la collection, la prochaine fois que vous créez le produit manquant, Humind le rattache à la collection.
Poussez les produits avant les collections
L'ordre le plus propre c'est : poussez vos produits d'abord, puis vos collections. Ça évite unresolved_products complètement. Si vous ne pouvez pas (par exemple vous reconstruisez les deux d'un coup), c'est quand même safe, collections et produits convergent une fois que les deux côtés sont là.
Les endpoints mono-resource (POST, PUT, PATCH /collections/{id}) suivent la même règle. Le body de la réponse inclut unresolved_products: [...] à chaque fois qu'au moins une entrée n'a pas résolu, top-level à côté de l'objet collection :
{
"external_id": "col-bestsellers",
"humind_id": "65f1ab9c8e7d4a2b1c3d4e5f",
"products": [
{ "external_id": "SKU-123", "include_all_variants": true },
{ "external_id": "SKU-DOES-NOT-EXIST-YET", "include_all_variants": true }
],
"unresolved_products": ["SKU-DOES-NOT-EXIST-YET"]
}Translations sub-resource
Pour les collections 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 /collections/{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. |
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/collections/api:col-bestsellers/translations/fr \
-H "Authorization: Bearer hmd_live_..." \
-H "Idempotency-Key: 5f6a7b8c-9d0e-4f1a-2b3c-4d5e6f7a8b9c" \
-H "Content-Type: application/json" \
-d '{
"title": "Nos meilleures ventes",
"description": "Les produits les plus aimés de la saison."
}'Response 200 OK
Renvoie l'objet collection 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 /collections/{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/collections/api:col-bestsellers/translations/fr \
-H "Authorization: Bearer hmd_live_..." \
-H "Idempotency-Key: 6a7b8c9d-0e1f-4a2b-3c4d-5e6f7a8b9c0d"Response 204 No Content
Pas de body.
Erreurs courantes
Les endpoints collections 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 à une collection qui appartient à votre company. | Vérifiez l'external_id ou le humind_id. Les lookups cross-tenant renvoient aussi 404. |
409 | idempotency_conflict | Même Idempotency-Key réutilisée avec un body différent. | Générez un UUID frais. |
409 | handle_already_used | Le handle soumis est déjà utilisé par une autre collection dans cette company (matched sur un autre external_id). | Choisissez un handle unique, ou faites un PATCH sur la collection existante (matched par external_id) au lieu d'en créer une nouvelle. |
400 | validation_failed | Le body a échoué le schéma Collection. 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. |
unresolved_products n'est pas une erreur
Vous verrez ce champ sur les réponses 200/201/207 quand au moins une entrée dans products[] n'a pas pu être matchée à un produit existant. C'est un heads-up, pas un échec, le reste de la requête a été appliqué normalement et Humind lie les références manquantes au moment où ces produits arrivent.