Méthode POST/partner/mint/batch

Envoyer un lot de lignes de produits, en JSON ou en CSV, jusqu'à 500 lignes par appel. Droit mint:batch. Les produits créés par cette voie sont identifiés par QR.

Sur cette page

Vous envoyez un lot de lignes de produits, et vous recevez un identifiant de lot. Nous répondons dès que nous acceptons le lot, avant tout traitement. Vous suivez ensuite l'avancement avec GET /v1/partner/mint/batch/status/{job_id}. Lisez l'avertissement ci-dessous avant de lire ce suivi.

Adresse complète :

HTTP
POST https://api.sealtrust.io/v1/partner/mint/batch

Le même point d'entrée répond aussi sans le préfixe /v1, à https://api.sealtrust.io/partner/mint/batch. Les deux adresses appellent le même code. Utilisez la forme /v1 pour une nouvelle intégration.

#Autorisation

Envoyez votre clef d'API dans l'en-tête Authorization, au format Bearer. La clef doit porter le droit mint:batch. Une clef qui ne porte pas ce droit reçoit un 403 dont le message nomme le droit manquant.

Trois conditions s'ajoutent au droit de la clef.

  • L'offre de la marque de la clef doit comprendre l'accès API. Sinon vous recevez un 403 portant le code FEATURE_NOT_AVAILABLE.
  • L'offre de la marque du lot doit autoriser l'identification par QR. C'est la méthode que le serveur pose sur chaque ligne de ce point d'entrée. Une offre qui ne l'autorise pas reçoit un 403 portant le code AUTH_METHOD_NOT_ALLOWED.
  • Chaque ligne porte le code public d'une marque que la clef sert : la sienne, ou, avec une clef revendeur, une marque cliente de votre contrat. Une seule ligne portant un autre code fait refuser le lot entier en 403. Toutes les lignes portent la même marque, sinon le lot est refusé en 422 ONE_BRAND_PER_BATCH. L'offre et le forfait appliqués sont ceux de cette marque.

#Plafond d'appels

Nous mesurons le débit sur une fenêtre fixe de 60 secondes. Nous appliquons d'abord la valeur posée sur votre compte, si nous en avons posé une. Sinon celle de votre offre. En l'absence des deux, la valeur de repli est de 120 appels par fenêtre.

Deux compteurs se superposent, avec le même plafond : un par clef, un pour la somme de toutes les clefs de votre marque. Créer des clefs supplémentaires n'augmente donc pas le débit total autorisé.

Dès que nous reconnaissons votre clef, la réponse porte quatre en-têtes. Ils décrivent le compteur le plus contraignant des deux. Un refus d'authentification arrive avant le comptage et ne porte aucun de ces en-têtes.

En-têteContenu
X-RateLimit-Limitle plafond appliqué sur la fenêtre
X-RateLimit-Remainingce qu'il vous reste dans la fenêtre en cours
X-RateLimit-Resetl'horodatage de fin de la fenêtre, en secondes
X-RateLimit-Scopekey ou brand, le compteur qui a servi de référence

Un refus de débit renvoie 429. Il porte ces quatre en-têtes, décrivant cette fois le compteur qui a refusé, et il ajoute Retry-After, exprimé en secondes restantes dans la fenêtre en cours. Cette valeur n'est jamais inférieure à 1.

Deux autres plafonds peuvent refuser le même appel : le quota quotidien de votre clef, qui se consomme par article, et, sur un compte d'essai seulement, le plafond de 10 produits par période de facturation. Vous les retrouvez dans le tableau des erreurs, plus bas.

#Paramètres de chemin et de requête

Ce point d'entrée n'a pas de paramètre de chemin, et un seul paramètre de requête, facultatif.

NomTypeObligatoireDescription
issue_purchase_codesbooleannontrue demande un code d'achat pour chaque pièce du lot. La clef doit aussi porter le droit claim_codes:issue, sinon 403 avant toute lecture du lot. Les codes arrivent une seule fois avec GET /v1/partner/mint/batch/{job_id}/items. Voir Émettre les codes d'achat.

#En-têtes

NomTypeObligatoireDescription
AuthorizationstringouiBearer suivi de votre clef d'API.
Content-Typestringouiapplication/json pour un lot en JSON, multipart/form-data pour un fichier CSV.
Idempotency-KeystringnonVotre propre identifiant d'appel. Nous le mémorisons 24 heures. Voir la section sur le rejeu.

#Corps de la requête

Vous envoyez le lot dans l'un des deux formats. Le contenu est le même dans les deux cas.

  • JSON : envoyez une liste d'objets. Un objet seul, un nombre ou une chaîne au premier niveau fait échouer la requête en 400.
  • CSV : envoyez un fichier en multipart/form-data, sous le champ de formulaire nommé exactement file. Encodez le fichier en UTF-8. Vous pouvez laisser la marque d'ordre d'octets en tête de fichier, nous la retirons. Un fichier encodé autrement fait échouer l'appel en 500. Dans un tableur, choisissez l'export « CSV UTF-8 ». Nous retirons les espaces de bord des cellules avant de valider.

Vous pouvez compresser le fichier CSV en gzip ou en zlib, nous le décompressons. Si le contenu décompressé dépasse environ 20 Mio, vous recevez un 413.

Un lot compte au maximum 500 articles. Nous refusons un lot vide en 400.

#Champs d'une ligne

Une ligne accepte sept champs, et sept seulement.

NomTypeObligatoireDescription
product_namestringouiLe nom du produit.
brand_codestringouiLe code public de la marque du lot, dix caractères, par exemple 7K3QXW9M2A. Il doit nommer la même marque sur toutes les lignes. C'est la marque de la clef, ou, avec une clef revendeur, une de vos marques clientes. GET /v1/partner/catalog/brands donne ces codes. L'ancien brand_id est encore accepté à sa place pour l'instant, mais il est déprécié, et s'écrit en chiffres seuls (12 ou "12"). Une ligne qui porte les deux, ou aucun des deux, est refusée en 400. Un code ou un numéro mal écrit est refusé en 422 dans un corps JSON, en 400 dans un CSV, voir les erreurs plus bas.
category_idintegerouiLe numéro de la catégorie du produit. La catégorie doit exister.
metadata_uristringouiL'adresse des métadonnées du produit.
model_skustringnonLe SKU d'un modèle de votre catalogue, tel que vous l'avez envoyé par PUT /v1/partner/catalog/brands/{brand_code}/models. La pièce est rattachée à ce modèle : elle reçoit son lien GS1 si le modèle a un GTIN, et sa page affiche le passeport du modèle. Sans ce champ, ni batch_code, la pièce n'a ni modèle ni lot.
batch_codestringnonLe code d'un lot de votre catalogue (PUT .../batches). La pièce est rattachée à ce lot et à son modèle, et sa page affiche le passeport du lot. Un lot déjà ancré sur la blockchain n'accepte plus aucune pièce.
external_refstringnonVotre propre référence. Nous la conservons sur la ligne du lot, et GET /partner/mint/batch/{job_id}/items vous la rend à côté du numéro de série de la pièce créée. Le produit créé, lui, ne la porte pas.

En CSV, l'en-tête doit contenir au minimum les quatre colonnes obligatoires. Les colonnes model_sku et batch_code sont facultatives, et une cellule vide vaut « pas de code ». L'ordre des colonnes est libre.

Le serveur n'ignore aucun champ. Tout champ que ce tableau ne nomme pas fait échouer la ligne, et donc le lot, en 400.

#Rejouer un appel sans doubler son effet

Vous pouvez envoyer un en-tête Idempotency-Key. Il est facultatif. Nous le mémorisons 24 heures.

  • Rejouez la même clef d'idempotence avec le même lot, et vous recevez la réponse du premier appel, sans qu'un second traitement démarre.
  • Rejouez la même clef d'idempotence avec un lot différent, et vous recevez un
    1. Ce comportement date du 20/08/2026. Auparavant, le second lot n'était jamais traité et rien ne le signalait.
  • Rejouez une clef d'idempotence dont l'appel est encore en cours de traitement, et vous recevez aussi un 409. Ce verrou expire de lui-même au bout de 60 secondes.

Nous comparons deux lots sur leur contenu une fois lu et normalisé. L'ordre des lignes, l'ordre des colonnes, le choix entre CSV et JSON et les cellules vides n'y changent rien.

#Requête d'exemple

Un lot de deux articles, en JSON.

curl -i -X POST https://api.sealtrust.io/v1/partner/mint/batch \
  -H "Authorization: Bearer st_live_0000000000000000000000000000000000000000000000" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: lot-exemple-0001" \
  -d '[
    {
      "product_name": "Sac Exemple 001",
      "brand_code": "7K3QXW9M2A",
      "category_id": 3,
      "metadata_uri": "ipfs://exemple-metadonnees-0001",
      "external_ref": "EX-0001"
    },
    {
      "product_name": "Sac Exemple 002",
      "brand_code": "7K3QXW9M2A",
      "category_id": 3,
      "metadata_uri": "ipfs://exemple-metadonnees-0002",
      "external_ref": "EX-0002"
    }
  ]'

Le même lot en CSV, envoyé en multipart/form-data. Le fichier batch.csv contient :

batch.csv
product_name,brand_code,category_id,metadata_uri,external_ref
Sac Exemple 001,7K3QXW9M2A,3,ipfs://exemple-metadonnees-0001,EX-0001
Sac Exemple 002,7K3QXW9M2A,3,ipfs://exemple-metadonnees-0002,EX-0002

Le SDK TypeScript n'expose pas l'envoi CSV. L'onglet TypeScript ci-dessous appelle donc directement le point d'entrée.

curl -i -X POST https://api.sealtrust.io/v1/partner/mint/batch \
  -H "Authorization: Bearer st_live_0000000000000000000000000000000000000000000000" \
  -H "Idempotency-Key: lot-exemple-0001" \
  -F "file=@batch.csv;type=text/csv"

#Réponse d'exemple

Code HTTP 200OK

Code HTTP 200.

JSON
{
  "job_id": "0000a1b2c3d4e5f6",
  "status": "queued",
  "items_count": 2,
  "brand_code": "7K3QXW9M2A",
  "brand_id": 12,
  "issue_purchase_codes": false
}

La réponse compte six champs et rien d'autre.

ChampTypeDescription
job_idstringL'identifiant du lot. Passez-le à GET /v1/partner/mint/batch/status/{job_id}.
statusstringVaut toujours queued sur cette réponse.
items_countintegerLe nombre de lignes acceptées dans le lot.
brand_codestringLe code public de la marque du lot.
brand_idintegerDéprécié : le numéro interne de la marque, gardé pendant la transition. Lisez brand_code.
issue_purchase_codesbooleantrue quand le lot a été envoyé avec des codes d'achat demandés.

status: "queued" veut dire que nous avons accepté le lot et qu'il attend son traitement. Nous n'avons encore rien traité à ce moment. Suivez la suite avec GET /v1/partner/mint/batch/status/{job_id}.

#Erreurs

Le corps d'une réponse d'erreur porte un champ detail. Selon le cas, ce champ contient une phrase ou un objet.

Un refus qui porte une phrase, ici le lot vide, code HTTP 400 :

JSON
{
  "detail": "Batch vide (aucun item)"
}

Un refus qui porte un objet, ici un en-tête CSV incomplet, code HTTP 400 :

JSON
{
  "detail": {
    "error": "Colonnes manquantes",
    "missing": ["category_id", "metadata_uri"]
  }
}
CodeConditionQue faire
400Le corps JSON envoyé est mal formé.Vérifiez que vous envoyez du JSON valide, ou un fichier CSV en multipart/form-data.
400Le corps JSON n'est pas une liste au premier niveau.Enveloppez votre objet dans une liste, même pour un article seul.
400Une ou plusieurs lignes JSON sont invalides. detail vaut {"error": "JSON invalide", "rows": [...]}, chaque entrée portant l'index de la ligne fautive et la raison.Corrigez les lignes signalées. Le premier objet de la liste porte l'index 1. Aucune ligne du lot n'a été traitée.
400Le fichier CSV est vide ou n'a pas de ligne d'en-tête. detail vaut {"error": "CSV vide ou sans en-têtes"}.Ajoutez la ligne d'en-tête.
400Une colonne obligatoire manque dans l'en-tête CSV. detail vaut {"error": "Colonnes manquantes", "missing": [...]}.Ajoutez les colonnes nommées dans missing. Le fichier n'a pas été lu au-delà de son en-tête.
400Une ou plusieurs lignes CSV sont invalides. detail vaut {"error": "CSV invalide", "rows": [...]}, chaque entrée portant la line du fichier, son external_ref et la raison.Corrigez les lignes signalées. La première ligne de données porte le numéro 2.
400Le fichier gzip envoyé est illisible.Renvoyez le fichier, ou envoyez-le sans compression.
400Le lot est vide.Envoyez au moins une ligne.
400Le lot dépasse 500 lignes. Le message donne le nombre reçu et le plafond.Découpez votre envoi en plusieurs appels de 500 lignes au maximum.
400Un ou plusieurs category_id ne correspondent à aucune catégorie. Le message liste les numéros introuvables.Corrigez les numéros de catégorie. La liste des catégories est visible dans la console.
401L'en-tête Authorization est absent. La réponse porte aussi WWW-Authenticate: Bearer.Ajoutez l'en-tête.
401L'en-tête Authorization ne commence pas par Bearer suivi d'un espace. La réponse porte aussi WWW-Authenticate: Bearer.Corrigez la forme de l'en-tête.
401La valeur envoyée après Bearer est vide ou fait moins de 40 caractères.Envoyez le secret complet, sans espace ni retour à la ligne.
401La clef envoyée est inconnue.Vérifiez que vous utilisez une clef de cet environnement, et qu'elle n'a pas été recréée.
403La clef n'est plus active, parce qu'elle a été révoquée. Le message donne son état.Créez une nouvelle clef dans la console.
403La clef a atteint sa date d'expiration.Créez une nouvelle clef. L'ancienne ne redeviendra jamais valide.
403La clef ne porte pas le droit mint:batch. Le message nomme le droit manquant.Créez une clef portant ce droit. Les droits d'une clef existante ne se modifient pas.
403issue_purchase_codes=true et la clef ne porte pas le droit claim_codes:issue. Le message nomme le droit manquant. Rien n'est lu ni réservé.Créez une clef portant les deux droits, ou envoyez le lot sans l'option.
403L'offre de votre marque ne comprend pas l'accès API. detail vaut {"code": "FEATURE_NOT_AVAILABLE", "feature": "api_access"}.Contactez-nous pour ouvrir ce droit sur votre offre. Réessayer ne changera rien.
403L'offre de votre marque n'autorise pas l'identification par QR, qui est la méthode posée par ce point d'entrée. detail porte le code AUTH_METHOD_NOT_ALLOWED.Contactez-nous pour ouvrir le QR sur votre offre. Réessayer ne changera rien.
403Au moins une ligne porte un brand_code que la clef ne sert pas : ni sa marque, ni une marque cliente de votre contrat revendeur. Un code qui ne nomme aucune marque reçoit la même réponse. detail vaut "This API key cannot act for this brand.".Corrigez les lignes. Aucune ligne du lot n'a été traitée.
403L'abonnement de la marque du lot n'est plus actif : période d'essai terminée (detail vaut TRIAL_EXPIRED) ou paiement en retard ou arrêté (detail vaut SUBSCRIPTION_INACTIVE), ou service suspendu pour une facture mensuelle impayée (detail vaut BILLING_SUSPENDED). La console refuse la même frappe.Régularisez l'abonnement ou la facture de cette marque. Rien n'a été réservé ni mis en file.
403Sur un compte d'essai, le lot ferait dépasser les 10 produits de la période de facturation. detail vaut {"code": "QUOTA_EXCEEDED", "resource": "products", "current": ..., "additional": ..., "max": ..., "period": "monthly"}.Attendez la période suivante, réduisez la taille du lot, ou souscrivez une offre. Ce refus ne consomme pas le quota quotidien de votre clef. Une offre vendue compte les pièces par année civile et facture celles au-delà des pièces comprises : ce refus ne se produit jamais pour des pièces.
404Un model_sku ou un batch_code ne correspond à aucun modèle ou lot de la marque de la clef. detail.code vaut UNKNOWN_CATALOGUE_REFERENCE et detail.rows nomme chaque ligne et le champ, jamais la valeur. Rien n'est mis en file.Créez le modèle ou le lot par l'API catalogue, ou corrigez le code.
409Le lot nommé par batch_code est déjà ancré sur la blockchain. detail.code vaut BATCH_ALREADY_ANCHORED. Rien n'est mis en file.Créez un nouveau lot et frappez sur celui-ci.
422Les lignes nomment plusieurs marques. detail.code vaut ONE_BRAND_PER_BATCH.Envoyez un lot par marque.
422Une ligne JSON porte un brand_code qui n'a pas la forme d'un code public, ou un brand_id qui n'est pas écrit en chiffres seuls (5.0, "+5", true). detail.code vaut BRAND_REF_NOT_WELL_FORMED et detail.references donne la ligne et le champ, jamais la valeur. Dans un CSV, la même faute est une ligne du refus 400 CSV invalide, dont le message commence par BRAND_REF_NOT_WELL_FORMED.Envoyez le code tel que GET /v1/partner/catalog/brands le donne. Rien n'a été mis en file.
422Le lot nommé par batch_code appartient à un autre modèle que le model_sku de la même ligne. detail.code vaut BATCH_OF_ANOTHER_MODEL.Envoyez le seul batch_code, qui apporte son modèle, ou corrigez le SKU.
409La clef d'idempotence a déjà servi pour un lot différent dans les 24 dernières heures.Utilisez une nouvelle clef d'idempotence pour ce lot. Ce lot n'a pas été traité.
409Un appel portant la même clef d'idempotence est en cours de traitement.Attendez la réponse du premier appel, puis relisez son résultat. Le verrou expire au bout de 60 secondes.
413Le fichier compressé dépasse environ 20 Mio une fois décompressé.Découpez votre fichier. Un lot de 500 lignes reste très en dessous de ce plafond.
429Le plafond de débit de la clef est atteint. Retry-After et la famille X-RateLimit-* accompagnent la réponse, avec X-RateLimit-Scope: key.Attendez le nombre de secondes indiqué par Retry-After, puis réessayez.
429Le plafond de débit de la marque est atteint, toutes clefs confondues. X-RateLimit-Scope vaut brand.Attendez le nombre de secondes indiqué par Retry-After. Créer une clef supplémentaire ne relève pas ce plafond.
429Le quota quotidien de la clef est atteint. Les en-têtes X-Quota-Limit, X-Quota-Remaining et X-Quota-Reset accompagnent la réponse.Attendez le passage de minuit en temps universel, ou faites relever le quota de la clef. Le quota se consomme par article : un lot de 100 lignes en consomme 100.
500Le fichier CSV envoyé n'est pas encodé en UTF-8.Réenregistrez le fichier en UTF-8. Dans un tableur, choisissez l'export « CSV UTF-8 ».
500Votre liste JSON contient un élément qui n'est pas un objet, par exemple une chaîne ou un nombre.Envoyez une liste dont chaque élément est un objet portant les champs décrits plus haut.
500Un category_id hors des bornes que la base accepte.Envoyez des numéros de catégorie visibles dans la console.
500La marque rattachée à votre clef est introuvable.Contactez-nous en indiquant l'heure de l'appel.
500Une erreur inattendue s'est produite pendant le traitement de votre appel.Réessayez. Si l'erreur persiste, contactez le support en indiquant l'heure de l'appel.
503Le service de plafonnement des appels est momentanément indisponible. Nous refusons alors l'appel.Réessayez dans quelques instants. Aucune ligne n'a été traitée.

#Voir aussi

Votre réponse ouvre un courriel pré-rempli dans votre messagerie, à destination de contact@sealtrust.io. Vous le relisez avant de l'envoyer.

Proposer une correctionSignaler un problème