Imports
L'API Imports est l'alternative async et streaming à POST /products. Vous récupérez auprès d'Humind une URL d'upload pré-signée, vous poussez un fichier NDJSON directement vers le blob storage, et Humind ingère le fichier en arrière-plan en utilisant exactement le même pipeline de validation et d'upsert que les endpoints synchrones.
À utiliser quand vous avez un gros catalogue à pousser (backfill initial, mirror quotidien de votre ERP, full re-sync après un changement de schéma) et que l'endpoint batch synchrone ne convient plus. Pas de round-trip HTTP par item, vous uploadez une fois, vous démarrez l'import une fois, et vous pollez le status.
Choisir votre endpoint
| Taille catalogue | Fréquence | Endpoint recommandé |
|---|---|---|
| 1 produit | One-off ou déclenché par webhook | POST /products |
| Jusqu'à 500 produits | Mise à jour bulk occasionnelle | POST /products/batch |
| 500+ produits, ou n'importe quelle taille sur un schedule récurrent | Sync complète quotidienne ou hebdo | POST /imports (cette page) |
L'API Imports et les endpoints synchrones écrivent dans le même catalogue, ce ne sont pas des stores séparés. Vous pouvez mixer : faites le backfill avec un import, puis gardez le catalogue à jour avec des appels POST /products depuis votre handler webhook.
Vue d'ensemble du workflow
Le cycle de vie complet d'un import a quatre étapes. Le merchant parle à l'API Humind aux étapes 1, 3 et 4 ; l'étape 2 est un upload direct vers Azure Blob Storage en utilisant l'URL qu'Humind a renvoyée à l'étape 1.
1. POST /imports → { sync_id, upload_url }
2. PUT <upload_url> (NDJSON body) → 201 depuis Azure Blob
3. POST /imports/{sync_id}/start → { status: "processing" }
4. GET /imports/{sync_id} → poll jusqu'à status="done" ou "failed"Une cinquième étape optionnelle, POST /imports/{sync_id}/cancel, arrête un import en cours au prochain checkpoint.
Scope requis pour chaque étape : imports:write. Le même scope couvre les reads.
Étape 1 : Créer l'import
POST /imports réserve un sync_id et renvoie une URL pré-signée à courte durée vers laquelle vous pouvez PUT votre fichier NDJSON.
Scope requis : imports:write
Request
curl -X POST https://api.thehumind.com/public/v1/imports \
-H "Authorization: Bearer hmd_live_..." \
-H "Idempotency-Key: 3f1a8d92-7c4b-4e6f-b2a1-d5e9c8f7a3b4" \
-H "Content-Type: application/json" \
-d '{
"resource_type": "product",
"format": "ndjson"
}'| Champ | Type | Requis | Description |
|---|---|---|---|
resource_type | 'product' | Oui | Le type de ressource contenu dans le fichier. Seul product est supporté aujourd'hui. |
format | 'ndjson' | Oui | Le format on-the-wire. Seul ndjson (newline-delimited JSON) est supporté aujourd'hui. |
Response 201 Created
{
"sync_id": "b5f9e3a2-7c1d-4e8a-9b3f-0c2d4e5f6a7b",
"status": "pending",
"upload_url": "https://<storage>.blob.core.windows.net/<container>/b5f9e3a2-7c1d-4e8a-9b3f-0c2d4e5f6a7b.ndjson?sv=2024-...&sig=...",
"expires_at": "2026-04-25T15:30:00Z"
}| Champ | Type | Description |
|---|---|---|
sync_id | string | Le handle de cet import. À utiliser dans le path de tous les appels suivants. |
status | string | Toujours pending à ce stade, le fichier n'a pas encore été uploadé. |
upload_url | string | L'URL pré-signée vers laquelle vous PUT votre NDJSON. Scope write/create sur ce seul blob ; ne peut pas être utilisée pour lire ou lister autre chose. |
expires_at | ISO 8601 | Quand l'upload_url arrête d'accepter les writes. Une heure après la création. |
L'URL d'upload expire en une heure
Si vous ne PUT pas votre fichier avant expires_at, l'URL devient inutilisable et vous devrez rappeler POST /imports pour en obtenir une fraîche. Le sync_id est cheap à recréer, n'essayez pas de réutiliser un stale.
Étape 2 : Upload de votre NDJSON
Une fois que vous avez l'upload_url, envoyez votre fichier directement vers Azure Blob Storage avec un seul PUT. Aucune authentification Humind n'intervient ici : le token SAS dans l'URL est la seule credential nécessaire, et il est déjà scopé pour write ce seul blob.
Format du fichier
NDJSON c'est « newline-delimited JSON » : un objet JSON par ligne, séparés par \n (pas \r\n), pas d'array englobant, pas de virgule entre les lignes, UTF-8. Chaque ligne doit être un payload Product valide, la même forme que POST /products accepte.
Exemple products.ndjson :
{"external_id":"SKU-001","title":"Crème hydratante","handle":"creme-hydratante","default_language":"fr","variants":[{"external_id":"SKU-001-50ML","title":"50 ml","price":29.90,"currency":"EUR"}]}
{"external_id":"SKU-002","title":"Sérum éclat","handle":"serum-eclat","default_language":"fr","variants":[{"external_id":"SKU-002-30ML","title":"30 ml","price":49.00,"currency":"EUR"}]}
{"external_id":"SKU-003","title":"Baume lèvres","handle":"baume-levres","default_language":"fr","variants":[{"external_id":"SKU-003-15ML","title":"15 ml","price":12.50,"currency":"EUR"}]}Request
curl -X PUT "$UPLOAD_URL" \
-H "Content-Type: application/x-ndjson" \
-H "x-ms-blob-type: BlockBlob" \
--upload-file products.ndjsonLe header x-ms-blob-type: BlockBlob est requis par Azure Blob Storage pour tous les uploads PUT. Le header Content-Type: application/x-ndjson est recommandé pour que le tooling downstream tague correctement le blob.
Response 201 Created
Azure renvoie 201 Created sans body en cas de succès. N'importe quel autre statut (typiquement 403 pour un SAS expiré, 400 pour un header requis manquant) veut dire que l'upload n'a pas atterri, corrigez la cause avant d'appeler start.
Gotchas NDJSON
- Un produit par ligne, terminé par
\n. Ne pas pretty-print, ça casse le splitter de lignes. - Pas de
[initial ni de]final, et pas de virgules entre les lignes. NDJSON n'est pas un array JSON. - UTF-8 uniquement. Si votre dataset a des caractères accentués, sauvegardez le fichier en UTF-8 sans BOM.
- Une ligne vide finale est OK et ignorée. Toute ligne qui ne parse pas est reportée dans
error_logsaprès l'exécution de l'import.
Étape 3 : Démarrer le traitement
POST /imports/{sync_id}/start passe l'import au worker en arrière-plan. Humind stream le blob ligne par ligne, valide chaque produit contre le même schéma que POST /products, et l'upsert dans votre catalogue.
Scope requis : imports:write
Request
curl -X POST https://api.thehumind.com/public/v1/imports/b5f9e3a2-7c1d-4e8a-9b3f-0c2d4e5f6a7b/start \
-H "Authorization: Bearer hmd_live_..." \
-H "Idempotency-Key: 7a2b3c4d-5e6f-4a8b-9c0d-1e2f3a4b5c6d"Response 202 Accepted
{
"status": "processing"
}Le corps minimal confirme que l'import a été mis en file. Utilisez GET /imports/{sync_id} pour l'état complet, y compris started_at, les compteurs et error_logs.
L'appel revient dès que l'import est en queue, généralement sous une seconde. L'ingestion réelle se passe en arrière-plan ; suivez la progression avec l'endpoint de polling.
Upload avant de start
Appeler start avant d'avoir PUT le blob avec succès renvoie 422 Unprocessable Entity avec le code import_blob_missing. Appeler start sur un import qui a déjà dépassé pending renvoie 422 avec le code import_not_pending.
Étape 4 : Poll status
GET /imports/{sync_id} renvoie l'état courant d'un import : compteurs jusqu'à présent, breakdown du report, et les dernières entrées d'error log.
Scope requis : imports:write
Request
curl https://api.thehumind.com/public/v1/imports/b5f9e3a2-7c1d-4e8a-9b3f-0c2d4e5f6a7b \
-H "Authorization: Bearer hmd_live_..."Response 200 OK : pendant le processing
{
"sync_id": "b5f9e3a2-7c1d-4e8a-9b3f-0c2d4e5f6a7b",
"status": "processing",
"resource_type": "product",
"total_products": 12000,
"synced_products": 4321,
"report": {
"created": 3210,
"updated": 1100,
"failed": 11
},
"error_logs": [
{
"message": "Validation failed on line 742: variants[0].price must be a number",
"product_id": "SKU-0742",
"timestamp": "2026-04-25T14:32:01Z"
}
],
"started_at": "2026-04-25T14:31:12Z",
"completed_at": null
}Response 200 OK : une fois terminé
{
"sync_id": "b5f9e3a2-7c1d-4e8a-9b3f-0c2d4e5f6a7b",
"status": "done",
"resource_type": "product",
"total_products": 12000,
"synced_products": 12000,
"report": {
"created": 9876,
"updated": 2100,
"failed": 24
},
"error_logs": [
{
"message": "Validation failed on line 1042: handle is required",
"product_id": "SKU-1042",
"timestamp": "2026-04-25T14:38:14Z"
}
],
"started_at": "2026-04-25T14:31:12Z",
"completed_at": "2026-04-25T14:41:55Z"
}Cadence de polling
Une fois toutes les 5 à 10 secondes suffit. Le champ status ne change qu'à des transitions connues (pending → processing → done|failed|cancelled), donc poller plus vite ne fait remonter rien de nouveau.
L'objet Import
| Champ | Type | Description |
|---|---|---|
sync_id | string | Identifiant stable de cet import. À utiliser dans tous les appels suivants. |
status | 'pending' | 'processing' | 'done' | 'failed' | 'cancelled' | État du cycle de vie. Voir Machine d'états ci-dessous. |
resource_type | 'product' | Type de ressource contenu dans le fichier. |
total_products | integer | Nombre de lignes parsées du fichier NDJSON. Set après que le fichier est entièrement lu ; peut être à 0 pendant pending ou les toutes premières secondes du processing. |
synced_products | integer | Nombre de lignes réussies créées ou mises à jour jusque-là. Les lignes en échec (validation, JSON malformé) ne sont pas comptées ici, elles vivent dans report.failed. Quand l'import est done, synced_products === report.created + report.updated et total_products = synced_products + report.failed. |
report.created | integer | Nombre de produits qui n'existaient pas avant et ont été insérés. |
report.updated | integer | Nombre de produits qui existaient déjà (matchés sur external_id) et ont été mis à jour en place. |
report.failed | integer | Nombre de lignes qui ont échoué la validation ou l'upsert. Ce compteur est exact même si certaines entrées ne sont plus présentes dans error_logs. |
error_logs | object[] | Les entrées d'échec les plus récentes. Cappé aux 100 dernières. Voir Entrées d'error log. |
upload_url | string | Renvoyé seulement par POST /imports. Pas répété par GET. |
expires_at | ISO 8601 | Expiration de l'URL d'upload. Renvoyé seulement par POST /imports. |
started_at | ISO 8601 | null | Quand start a été appelé. null pendant pending. |
completed_at | ISO 8601 | null | Quand l'import a atteint done, failed, ou cancelled. null jusque-là. |
created_at | ISO 8601 | Quand l'import a été créé (appel à POST /imports). |
Machine d'états
pending ──start──▶ processing ──finish──▶ done
│ │
│ ├──fatal error──▶ failed
│ └──cancel──────▶ cancelled
└──cancel──▶ cancelledUne fois qu'un import atteint un état terminal (done, failed, cancelled), il y reste. Re-faire tourner un import veut dire en créer un nouveau avec POST /imports et uploader un fichier frais.
Annuler un import
POST /imports/{sync_id}/cancel arrête un import qui est encore pending ou processing. C'est best-effort : un import en processing continue de tourner jusqu'au prochain checkpoint (typiquement toutes les quelques centaines de lignes), moment où le worker remarque le flag de cancel et s'arrête proprement.
Scope requis : imports:write
Request
curl -X POST https://api.thehumind.com/public/v1/imports/b5f9e3a2-7c1d-4e8a-9b3f-0c2d4e5f6a7b/cancel \
-H "Authorization: Bearer hmd_live_..." \
-H "Idempotency-Key: 9b8a7c6d-5e4f-4a3b-2c1d-0e9f8a7b6c5d"Response 200 OK
{
"status": "cancelled"
}Utilisez ensuite GET /imports/{sync_id} pour l'état complet, y compris completed_at et les compteurs.
Les produits qui ont déjà été créés ou mis à jour avant le point de cancel restent dans votre catalogue. Le cancel ne fait pas de rollback. Si vous devez annuler les changements, poussez un import corrigé ou utilisez les endpoints synchrones PATCH/DELETE pour corriger des entrées spécifiques.
Annuler un import done, failed, ou déjà cancelled renvoie 422 avec le code import_not_pending.
Entrées d'error log
Chaque entrée dans error_logs décrit une ligne du fichier NDJSON qui n'a pas atterri dans le catalogue.
| Champ | Type | Description |
|---|---|---|
message | string | Résumé lisible de l'échec incluant le numéro de ligne, par exemple Validation failed on line 3: Required ou Invalid JSON on line 4: Expected property name…. La chaîne est suffisamment stable pour grep la classe (Validation failed, Invalid JSON) mais ce n'est pas le code d'erreur machine. Pour debug par champ, rejouez la ligne fautive via POST /products. |
product_id | string | L'external_id de la ligne fautive, quand il a pu être parsé. Omis quand la ligne était du JSON malformé (le message lui-même contient le numéro de ligne, par exemple Invalid JSON on line 1042: …). |
timestamp | ISO 8601 | Quand le worker a vu l'échec. |
La liste est cappée aux 100 derniers échecs pour borner le payload de réponse. Le compteur report.failed reste exact peu importe. Si vous devez débugger un import avec des milliers d'échecs, corrigez l'erreur la plus commune dans votre source data, ré-exportez, et lancez un import frais.
Débugger une ligne en échec
- Regardez
messagepour identifier la classe d'erreur (le plus souventvalidation_failed). - Utilisez
product_idpour retrouver l'entrée fautive dans votre export source. - Essayez le même payload via
POST /productspour avoir le feedback de validation complet dans la réponse, l'endpoint synchrone renvoie le path de champ précis danserror.details, ce que le log d'import abrège. - Corrigez la source data et re-faites tourner l'import.
Limites et quotas
| Limite | Valeur | Notes |
|---|---|---|
| Durée de vie de l'URL d'upload | 1 heure | Après expires_at, le token SAS arrête d'accepter les requêtes PUT. Re-créez l'import pour avoir une URL fraîche. |
| Lignes par fichier | Pas de cap dur aujourd'hui | Les fichiers au-delà d'1 000 000 de lignes peuvent prendre longtemps à ingérer ; envisagez de splitter en plusieurs imports si vous êtes au-delà. |
| Imports concurrents par company | Pas de cap dur aujourd'hui | Queuez vos imports vous-même si vous voulez un ordre strict. |
| Types de ressource | product uniquement | D'autres types de ressources pourront être ajoutés ultérieurement. |
| Format de fichier | NDJSON uniquement | Les formats CSV et JSON-array ne sont pas supportés et pas prévus pour le moment. |
Webhooks
Skipper le polling avec les webhooks
Abonnez les events import.completed et import.failed à un endpoint webhook et Humind vous POST le payload final dès que l'import atteint un état terminal. Le body reflète l'objet Import, donc vous pouvez complètement supprimer votre boucle de polling. Voir Webhooks › Events disponibles.
Mode test
Les imports héritent du mode test/live de la clé API qui les a créés. Si votre clé est hmd_test_*, chaque produit ingéré par l'import reste isolé de votre catalogue live. Un import live (hmd_live_*) ne peut pas créer ou modifier des produits test, et inversement. Voir Authentication pour le modèle test-mode complet.
Erreurs courantes
L'API Imports peut renvoyer n'importe lequel des statuts HTTP standards, mais voici les codes spécifiques à cette ressource :
| Statut | Code | Quand | Fix |
|---|---|---|---|
403 | insufficient_scope | La clé n'a pas imports:write. | Créez une nouvelle clé avec imports:write. |
404 | not_found | Le {sync_id} ne correspond pas à un import qui appartient à votre company. | Vérifiez le sync_id. Les lookups cross-tenant renvoient aussi 404. |
422 | import_not_pending | start ou cancel a été appelé sur un import qui n'est pas dans le bon état (par exemple start sur un import processing/done/failed/cancelled). | Vérifiez status d'abord. Chaque transition du cycle de vie est à sens unique. |
422 | import_blob_missing | start a été appelé avant que le fichier NDJSON ne soit uploadé sur upload_url. | PUT le blob d'abord, puis appelez start. |
400 | validation_failed | Le body de POST /imports n'avait pas resource_type ou utilisait une valeur non supportée. | Corrigez le body et renvoyez. |
Pour aller plus loin
- Products : le schéma produit que chaque ligne NDJSON doit suivre.
- Authentication : générer une clé
imports:write. - Errors : référence complète des codes d'erreur.