Importer un catalogue
Soumettez jusqu'à 2 000 produits en un seul appel, une ligne par produit avec les URL de ses photos. Nous récupérons les images, validons tout au préalable et rendons compte de chaque ligne.
Un import est un job. Vous soumettez des lignes, vous recevez immédiatement un identifiant d’import, et les produits sont créés en arrière-plan pendant que vous interrogez le job ou attendez un webhook. Rien n’est créé tant que chaque ligne n’a pas passé la validation, et une fois les lignes acceptées, elles réussissent ou échouent indépendamment les unes des autres.
Soumettre
POST/v1/imports
Deux formats de corps de requête transportent les mêmes lignes.
# Un objet JSON par ligne. Les options vont dans la chaîne de requête.curl "https://api.veeton.com/v1/imports?mode=upsert&on_image_error=skip" \ -H "Authorization: Bearer $VEETON_KEY" \ -H "Content-Type: application/x-ndjson" \ -H "Idempotency-Key: $(uuidgen)" \ --data-binary @catalogue.jsonl# Une enveloppe avec les options et un tableau rows.curl https://api.veeton.com/v1/imports \ -H "Authorization: Bearer $VEETON_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d @- <<'JSON'{ "mode": "upsert", "on_image_error": "skip", "rows": [ … ] }JSONChaque ligne correspond à un produit :
{ "reference": "TEE-SAGE-001", "name": "Crewneck Tee — Sage", "description": "Heavyweight organic cotton.", "sex": "female", "images": [ { "url": "https://cdn.example.com/tee-sage-front.jpg", "orientation": "front" }, { "url": "https://cdn.example.com/tee-sage-back.jpg", "orientation": "back" }, { "image_id": "01HX5K2MZ7A3Q4FBNDC0EVDXYS" } ]}| Champ | Règles |
|---|---|
reference |
Obligatoire. Votre code produit. Unique au sein du fichier et parmi vos produits créés via l’API. 100 caractères maximum. |
name |
Obligatoire. 200 caractères maximum. |
description |
Facultatif. 2 000 caractères maximum. |
sex |
Facultatif. male, female, other ou kid. |
images |
De 1 à 20 entrées. Chacune comporte exactement l’un des deux champs url ou image_id, et un champ orientation facultatif (omettez-le pour laisser la photo unlabeled en vue de l’annotation automatique). |
L’url d’une image doit être en https://, sur un hôte public et le port par défaut, sans identifiants intégrés, et récupérable sans authentification pendant au moins 24 heures. Les URL S3 présignées et les URL de CDN sont la réponse habituelle. 10 Mo maximum par image ; JPEG, PNG et WebP, détectés à partir des octets. Un image_id désigne une image déjà envoyée avec POST /v1/images.
Les corps de requête sont limités à 8 Mo. Comptez environ 4 Ko par ligne lorsque les photos sont des URL présignées (une URL fait ~350 octets ; une ligne avec huit photos et une description complète dépasse 5 Ko) : un catalogue peut atteindre le plafond avant la limite de 2 000 lignes. Répartissez-le sur plusieurs appels, ou envoyez d’abord les photos et référencez-les par image_id. Un corps au-dessus du plafond est refusé avec 413 body_too_large une fois lu.
La validation est tout ou rien
Chaque ligne est vérifiée avant toute création. Si une seule ligne est invalide, la soumission entière est refusée avec un 400 et chaque problème est listé par ligne, afin que vous corrigiez le fichier une seule fois avant de le soumettre à nouveau.
{ "errors": [ { "line": 12, "code": "invalid_row", "param": "images.0.url", "message": "`url` must use https" }, { "line": 340, "code": "duplicate_reference_in_file", "param": "reference", "message": "reference \"TEE-SAGE-001\" already appears on line 12. Each row must have a distinct reference." } ]}Modes
mode détermine ce que fait une ligne lorsqu’un produit portant la même reference existe déjà parmi les produits que vous avez créés via l’API. Les produits créés dans le tableau de bord ne portent pas de référence et ne sont jamais mis en correspondance.
| Mode | Référence existante | Nouvelle référence |
|---|---|---|
upsert (par défaut) |
Mis à jour sur place ; les images de la ligne sont ajoutées à la suite | Créé |
create |
La ligne échoue avec reference_already_exists |
Créé |
skip_existing |
Laissé intact ; la ligne est signalée skipped |
Créé |
C’est upsert qui rend sûre la resoumission d’un fichier après un délai d’attente dépassé : rien n’est dupliqué.
Lorsqu’une image ne peut pas être récupérée
on_image_error détermine l’effet d’une image en échec sur sa ligne.
skip(par défaut) : attacher les images qui ont fonctionné et signaler la ligne commepartial.fail_row: ne rien créer pour cette ligne et la signaler commefailed. Les images déjà récupérées pour la ligne sont abandonnées.
Une ligne dont toutes les images échouent est failed dans les deux modes.
Suivre le job
GET/v1/imports/{import_id}
{ "id": "01HX5K2MZ7A3Q4FBNDC0EVDXIM", "status": "processing", "mode": "upsert", "on_image_error": "skip", "counts": { "total": 1840, "pending": 412, "succeeded": 1401, "partial": 22, "failed": 5 }, "url": null, "url_expires_at": null, "created_at": "2026-09-03T14:30:09Z", "started_at": "2026-09-03T14:30:11Z", "finished_at": null}Les compteurs sont tenus à jour au fur et à mesure que les lignes se terminent, si bien qu’une interrogation coûte une seule lecture indexée, quelle que soit la taille du job. Tant que le job est en processing, la réponse porte Retry-After: 5 ; c’est l’intervalle auquel interroger le job.
| Statut | Signification |
|---|---|
processing |
Les lignes sont en cours de traitement. |
completed |
Chaque ligne a réussi. |
completed_with_errors |
Le job est terminé, mais certaines lignes sont partial ou failed. |
failed |
Le job lui-même n’a pas pu s’exécuter. Aucune ligne n’a été traitée. |
Ou abonnez-vous : un webhook actif sur import.completed et import.failed reçoit l’identifiant de l’import lorsque le job atteint un état terminal. Un import comportant quelques lignes en échec est tout de même completed ; le détail ligne par ligne se trouve sur le job.
Résultats par ligne
GET/v1/imports/{import_id}/rows
Une entrée par ligne soumise, dans l’ordre d’entrée, avec ce qu’il en est advenu. status=failed restreint aux lignes qui demandent votre attention, ce qui constitue la boucle corriger-et-resoumettre.
{ "data": [ { "line": 87, "reference": "TEE-OAT-002", "status": "failed", "action": "none", "product_id": null, "images": [ { "index": 0, "source": "https://cdn.example.com/oat-front.jpg", "orientation": "front", "status": "failed", "error": { "code": "http_error", "message": "origin answered 403 (HTTP 403)" } } ], "error": { "code": "no_images", "message": "none of the row's images could be attached" } } ], "next_cursor": null, "has_more": false}Une fois le job terminal, url sur l’import pointe vers un fichier JSONL contenant les mêmes objets, un par ligne, dans l’ordre d’entrée. Utilisez le fichier pour un rapprochement de l’ensemble du catalogue et l’endpoint pour les boucles programmatiques. L’URL est re-signée à chaque lecture et url_expires_at indique quand elle cesse de fonctionner ; mettez le fichier en cache, pas l’URL.
Les codes d’erreur d’image sont stables : http_error, timeout, too_large, not_an_image, blocked_host, dns_failed, image_not_found, row_failed. De même pour les codes d’erreur de ligne : reference_already_exists, no_images, image_failed, too_many_attempts.