Skip to content

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 catalogueFréquenceEndpoint recommandé
1 produitOne-off ou déclenché par webhookPOST /products
Jusqu'à 500 produitsMise à jour bulk occasionnellePOST /products/batch
500+ produits, ou n'importe quelle taille sur un schedule récurrentSync complète quotidienne ou hebdoPOST /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

bash
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"
  }'
ChampTypeRequisDescription
resource_type'product'OuiLe type de ressource contenu dans le fichier. Seul product est supporté aujourd'hui.
format'ndjson'OuiLe format on-the-wire. Seul ndjson (newline-delimited JSON) est supporté aujourd'hui.

Response 201 Created

json
{
  "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"
}
ChampTypeDescription
sync_idstringLe handle de cet import. À utiliser dans le path de tous les appels suivants.
statusstringToujours pending à ce stade, le fichier n'a pas encore été uploadé.
upload_urlstringL'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_atISO 8601Quand 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

bash
curl -X PUT "$UPLOAD_URL" \
  -H "Content-Type: application/x-ndjson" \
  -H "x-ms-blob-type: BlockBlob" \
  --upload-file products.ndjson

Le 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_logs aprè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

bash
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

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

bash
curl https://api.thehumind.com/public/v1/imports/b5f9e3a2-7c1d-4e8a-9b3f-0c2d4e5f6a7b \
  -H "Authorization: Bearer hmd_live_..."

Response 200 OK : pendant le processing

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

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

ChampTypeDescription
sync_idstringIdentifiant 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_productsintegerNombre 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_productsintegerNombre 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.createdintegerNombre de produits qui n'existaient pas avant et ont été insérés.
report.updatedintegerNombre de produits qui existaient déjà (matchés sur external_id) et ont été mis à jour en place.
report.failedintegerNombre 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_logsobject[]Les entrées d'échec les plus récentes. Cappé aux 100 dernières. Voir Entrées d'error log.
upload_urlstringRenvoyé seulement par POST /imports. Pas répété par GET.
expires_atISO 8601Expiration de l'URL d'upload. Renvoyé seulement par POST /imports.
started_atISO 8601 | nullQuand start a été appelé. null pendant pending.
completed_atISO 8601 | nullQuand l'import a atteint done, failed, ou cancelled. null jusque-là.
created_atISO 8601Quand l'import a été créé (appel à POST /imports).

Machine d'états

pending  ──start──▶  processing  ──finish──▶  done
   │                      │
   │                      ├──fatal error──▶  failed
   │                      └──cancel──────▶  cancelled
   └──cancel──▶  cancelled

Une 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

bash
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

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

ChampTypeDescription
messagestringRé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_idstringL'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: …).
timestampISO 8601Quand 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

  1. Regardez message pour identifier la classe d'erreur (le plus souvent validation_failed).
  2. Utilisez product_id pour retrouver l'entrée fautive dans votre export source.
  3. Essayez le même payload via POST /products pour avoir le feedback de validation complet dans la réponse, l'endpoint synchrone renvoie le path de champ précis dans error.details, ce que le log d'import abrège.
  4. Corrigez la source data et re-faites tourner l'import.

Limites et quotas

LimiteValeurNotes
Durée de vie de l'URL d'upload1 heureAprè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 fichierPas de cap dur aujourd'huiLes 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 companyPas de cap dur aujourd'huiQueuez vos imports vous-même si vous voulez un ordre strict.
Types de ressourceproduct uniquementD'autres types de ressources pourront être ajoutés ultérieurement.
Format de fichierNDJSON uniquementLes 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 :

StatutCodeQuandFix
403insufficient_scopeLa clé n'a pas imports:write.Créez une nouvelle clé avec imports:write.
404not_foundLe {sync_id} ne correspond pas à un import qui appartient à votre company.Vérifiez le sync_id. Les lookups cross-tenant renvoient aussi 404.
422import_not_pendingstart 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.
422import_blob_missingstart a été appelé avant que le fichier NDJSON ne soit uploadé sur upload_url.PUT le blob d'abord, puis appelez start.
400validation_failedLe 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.

Released under the proprietary Humind license.