API
esc

Saisissez votre recherche.

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": [ … ] }
JSON

Chaque ligne correspond à un produit :

catalogue.jsonl (une ligne)
{
"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.

400 Bad Request
{
"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 comme partial.
  • fail_row : ne rien créer pour cette ligne et la signaler comme failed. 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}
200 OK
{
"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.

GET /v1/imports/{id}/rows?status=failed
{
"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.