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 :
POST https://api.sealtrust.io/v1/partner/mint/batchLe 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ête | Contenu |
|---|---|
X-RateLimit-Limit | le plafond appliqué sur la fenêtre |
X-RateLimit-Remaining | ce qu'il vous reste dans la fenêtre en cours |
X-RateLimit-Reset | l'horodatage de fin de la fenêtre, en secondes |
X-RateLimit-Scope | key 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.
| Nom | Type | Obligatoire | Description |
|---|---|---|---|
issue_purchase_codes | boolean | non | true 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
| Nom | Type | Obligatoire | Description |
|---|---|---|---|
Authorization | string | oui | Bearer suivi de votre clef d'API. |
Content-Type | string | oui | application/json pour un lot en JSON, multipart/form-data pour un fichier CSV. |
Idempotency-Key | string | non | Votre 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é exactementfile. 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.
| Nom | Type | Obligatoire | Description |
|---|---|---|---|
product_name | string | oui | Le nom du produit. |
brand_code | string | oui | Le 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_id | integer | oui | Le numéro de la catégorie du produit. La catégorie doit exister. |
metadata_uri | string | oui | L'adresse des métadonnées du produit. |
model_sku | string | non | Le 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_code | string | non | Le 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_ref | string | non | Votre 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
- 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"
}
]'import { SealTrustClient } from "@sealtrust-io/sdk";
const sealtrust = new SealTrustClient({
apiKey: "st_live_0000000000000000000000000000000000000000000000",
});
const lot = await sealtrust.products.mint(
[
{
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",
},
],
"lot-exemple-0001",
);
console.log(lot.job_id, lot.status, lot.items_count, lot.brand_code);import requests
lot = [
{
"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",
},
]
response = requests.post(
"https://api.sealtrust.io/v1/partner/mint/batch",
headers={
"Authorization": "Bearer st_live_0000000000000000000000000000000000000000000000",
"Idempotency-Key": "lot-exemple-0001",
},
json=lot,
timeout=30,
)
print(response.status_code)
print(response.headers["X-RateLimit-Remaining"])
print(response.json())Le même lot en CSV, envoyé en multipart/form-data. Le fichier batch.csv
contient :
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-0002Le 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"import { readFile } from "node:fs/promises";
const contenu = await readFile("batch.csv");
const formulaire = new FormData();
formulaire.append("file", new Blob([contenu], { type: "text/csv" }), "batch.csv");
const reponse = await fetch("https://api.sealtrust.io/v1/partner/mint/batch", {
method: "POST",
headers: {
Authorization:
"Bearer st_live_0000000000000000000000000000000000000000000000",
"Idempotency-Key": "lot-exemple-0001",
},
body: formulaire,
});
console.log(reponse.status);
console.log(reponse.headers.get("X-RateLimit-Remaining"));
console.log(await reponse.json());import requests
with open("batch.csv", "rb") as fichier:
response = requests.post(
"https://api.sealtrust.io/v1/partner/mint/batch",
headers={
"Authorization": "Bearer st_live_0000000000000000000000000000000000000000000000",
"Idempotency-Key": "lot-exemple-0001",
},
files={"file": ("batch.csv", fichier, "text/csv")},
timeout=30,
)
print(response.status_code)
print(response.headers["X-RateLimit-Remaining"])
print(response.json())#Réponse d'exemple
Code HTTP 200OK
Code HTTP 200.
{
"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.
| Champ | Type | Description |
|---|---|---|
job_id | string | L'identifiant du lot. Passez-le à GET /v1/partner/mint/batch/status/{job_id}. |
status | string | Vaut toujours queued sur cette réponse. |
items_count | integer | Le nombre de lignes acceptées dans le lot. |
brand_code | string | Le code public de la marque du lot. |
brand_id | integer | Déprécié : le numéro interne de la marque, gardé pendant la transition. Lisez brand_code. |
issue_purchase_codes | boolean | true 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 :
{
"detail": "Batch vide (aucun item)"
}Un refus qui porte un objet, ici un en-tête CSV incomplet, code HTTP 400 :
{
"detail": {
"error": "Colonnes manquantes",
"missing": ["category_id", "metadata_uri"]
}
}| Code | Condition | Que faire |
|---|---|---|
| 400 | Le corps JSON envoyé est mal formé. | Vérifiez que vous envoyez du JSON valide, ou un fichier CSV en multipart/form-data. |
| 400 | Le corps JSON n'est pas une liste au premier niveau. | Enveloppez votre objet dans une liste, même pour un article seul. |
| 400 | Une 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. |
| 400 | Le 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. |
| 400 | Une 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. |
| 400 | Une 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. |
| 400 | Le fichier gzip envoyé est illisible. | Renvoyez le fichier, ou envoyez-le sans compression. |
| 400 | Le lot est vide. | Envoyez au moins une ligne. |
| 400 | Le 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. |
| 400 | Un 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. |
| 401 | L'en-tête Authorization est absent. La réponse porte aussi WWW-Authenticate: Bearer. | Ajoutez l'en-tête. |
| 401 | L'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. |
| 401 | La 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. |
| 401 | La clef envoyée est inconnue. | Vérifiez que vous utilisez une clef de cet environnement, et qu'elle n'a pas été recréée. |
| 403 | La 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. |
| 403 | La clef a atteint sa date d'expiration. | Créez une nouvelle clef. L'ancienne ne redeviendra jamais valide. |
| 403 | La 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. |
| 403 | issue_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. |
| 403 | L'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. |
| 403 | L'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. |
| 403 | Au 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. |
| 403 | L'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. |
| 403 | Sur 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. |
| 404 | Un 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. |
| 409 | Le 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. |
| 422 | Les lignes nomment plusieurs marques. detail.code vaut ONE_BRAND_PER_BATCH. | Envoyez un lot par marque. |
| 422 | Une 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. |
| 422 | Le 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. |
| 409 | La 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é. |
| 409 | Un 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. |
| 413 | Le 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. |
| 429 | Le 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. |
| 429 | Le 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. |
| 429 | Le 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. |
| 500 | Le 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 ». |
| 500 | Votre 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. |
| 500 | Un category_id hors des bornes que la base accepte. | Envoyez des numéros de catégorie visibles dans la console. |
| 500 | La marque rattachée à votre clef est introuvable. | Contactez-nous en indiquant l'heure de l'appel. |
| 500 | Une 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. |
| 503 | Le 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
GET /partner/mint/batch/status/{job_id}, suivre l'avancement d'un lot envoyé à la frappe.- Créer des produits, à l'unité et en lot, créer un produit, créer une série entière, importer un fichier.
- API partenaire, vue d'ensemble, adresse de base, clefs, droits, plafonds d'appels et pagination.
- Émettre les codes d'achat, un code par pièce, montré une fois, au moment de la frappe ou après.
- Erreurs de l'API, reconnaître un code d'erreur et décider s'il faut corriger ou rejouer.
Cette page vous a-t-elle été utile ?
Votre réponse ouvre un courriel pré-rempli dans votre messagerie, à destination de contact@sealtrust.io. Vous le relisez avant de l'envoyer.