Journal des changements

Ce qui a changé dans l'API partenaire et dans les notifications, par date, avec le geste exact à faire quand une entrée casse un appel qui passait avant.

En quittant cette page, vous saurez ce qui a changé dans l'API partenaire et dans les notifications, à quelle date, et ce que vous avez à faire de votre côté quand un changement refuse un appel qui passait avant. Vous saurez aussi pourquoi le numéro de version que vous appelez ne bouge pas alors que ces entrées s'accumulent.

Lisez cette page en entier la première fois. Ensuite, revenez-y avant chaque évolution de votre intégration et arrêtez-vous à la première date que vous connaissez déjà.

#Comment cette page est faite

Vous lisez les entrées de la plus récente à la plus ancienne. La date portée par une entrée est celle de la mise en service du changement.

Une entrée commence par Rupture de compatibilité quand un appel qui passait avant est refusé après. C'est le seul cas où vous avez du travail. Les autres entrées décrivent un comportement qui s'ajoute ou une réponse qui devient plus précise, et votre intégration continue de fonctionner sans retouche.

Chaque entrée dit trois choses : ce qui change, la réponse exacte que vous recevez maintenant, et le geste à faire. Aucune entrée ne renvoie ailleurs pour le geste à faire.

Cette page couvre tous les points d'entrée que ce site documente : l'API partenaire à clef, les points d'entrée publics sans clef, les lectures de passeport authentifiées, les notifications envoyées à vos adresses et le portail partenaire. Elle ne couvre pas la console, dont les écrans évoluent sans contrat à tenir envers votre code.

#Pourquoi la version ne bouge pas

Le point d'entrée GET /v1/version répond toujours la même chose.

JSON
{
  "version": "1.0.0",
  "api_version": "v1"
}

Les entrées ci-dessous s'inscrivent toutes dans la version 1. Le jour où une évolution ne pourra pas y tenir, elle s'ajoutera sous /v2, et la version 1 gardera son comportement.

Les points d'entrée métier existent à deux adresses qui appellent le même code : avec le préfixe /v1, et sans aucun préfixe. Appelez GET /v1/version quand vous voulez la version de contrat. C'est le seul point d'entrée de version que ce site documente.

Les deux adresses appellent le même code, donc elles reçoivent en même temps tous les changements de comportement listés ici. Une seule entrée fait exception, celle du 16 août 2026, qui ne concernait que les adresses sans préfixe.

Sur cette page
  1. #

    1. Changement

      #Une pièce garde son identité quand sa marque change d'offre

      Une pièce créée sur l'offre Conformité + Identité garde ses fonctions d'identité quand sa marque passe sur l'offre Conformité : la revendication avec son code d'achat, la fin d'une revendication commencée, les transferts (les commencer, les accepter, les refuser), la lecture de sa puce NFC, la demande de réparation et l'espace du propriétaire. Avant, ces appels répondaient 403 OFFER_EXCLUDES_IDENTITY pour toute pièce d'une marque en offre Conformité.

      Détail

      La réponse que vous recevez maintenant. Ces appels répondent comme sur l'offre Conformité + Identité. GET /timeline/{identifier} et GET /my-products portent identity_included à true pour ces pièces. Créer une pièce, commander un encodage de puces et émettre un nouveau code d'achat répondent toujours 403 OFFER_EXCLUDES_IDENTITY sur l'offre Conformité.

      Ce que vous faites. Rien. Si votre intégration masquait la revendication ou le transfert d'après l'offre de la marque, lisez plutôt identity_included de la pièce.

    2. Changement

      #Le serveur MCP d'administration s'ajoute à Claude avec votre compte

      Le serveur d'administration, qui prépare le catalogue d'une marque sans jamais publier, est servi à l'adresse https://mcp.sealtrust.io/admin/mcp. Vous l'ajoutez comme connecteur personnalisé dans Claude, vous vous connectez à la console SealTrust, vous choisissez une marque et ce que l'assistant peut faire. Aucune clé à copier. Le programme lancé sur votre poste avec une clé reste disponible et ne change pas.

      Détail

      La réponse que vous recevez maintenant. Sans jeton, POST /admin/mcp répond 401 avec un en-tête WWW-Authenticate qui donne l'adresse des métadonnées de la ressource. Claude suit cette adresse, s'enregistre, puis vous envoie sur la page « Connecter un assistant IA » de la console. Un jeton vaut 30 minutes, une connexion 90 jours au plus. Au-delà de 60 appels d'outil par minute et par connexion, la réponse est 429 avec un en-tête Retry-After. Un compte de l'équipe SealTrust ne peut pas être connecté. « Préparer des brouillons » n'est jamais coché d'avance, et changer ou réinitialiser votre mot de passe met fin à vos connexions. Le détail est dans Brancher le serveur MCP.

      Ce que vous faites. Rien pour une intégration existante. Pour mettre fin à une connexion, ouvrez Paramètres, puis Développeurs, carte « Assistants IA connectés ».

  2. #

    1. Rupture de compatibilité

      #chaque notification est signée, le secret est obligatoire

      Un abonnement aux notifications a maintenant toujours un secret de signature, et nous refusons d'envoyer une livraison qui ne serait pas signée. Sur POST /v1/partner/webhooks, un secret fourni compte de 32 à 128 caractères. Sans secret, nous en générons un de 43 caractères. Sur PUT /v1/partner/webhooks/{webhook_id}, le secret ne s'efface plus : null, une chaîne vide ou un secret de moins de 32 caractères sont refusés.

      Détail

      La réponse que vous recevez maintenant. Un 422 pour un secret trop court ou vide, et pour un secret null dans une modification, avec loc qui nomme secret. Une création sans secret répond 201 avec un champ secret en plus : c'est la seule fois qu'il est lisible. Une création avec votre secret ne le renvoie pas, comme avant.

      Ce que vous faites. Si votre secret fait moins de 32 caractères, prenez-en un plus long. Si vous créez sans secret, lisez le champ secret de la réponse et configurez-le sur votre récepteur. Pour garder le secret actuel dans une modification, omettez le champ au lieu d'envoyer null.

    2. Changement

      #Un réessai suit l'abonnement tel qu'il est au moment de partir

      Un réessai relit l'abonnement juste avant d'être envoyé. Il part vers l'adresse actuelle, signé avec le secret actuel. Il est abandonné sans envoi quand l'abonnement a été supprimé, éteint, désabonné de l'événement, ou quand votre offre ne comprend plus les notifications. Avant, un réessai déjà programmé partait vers l'ancienne adresse avec l'ancien secret.

      Détail

      La réponse que vous recevez maintenant. Après un changement de secret, les réessais en attente arrivent signés avec le nouveau secret.

      Ce que vous faites. Rien, si votre récepteur accepte les deux secrets pendant une bascule, comme cette documentation le conseille.

    3. Changement

      #Le serveur MCP s'ajoute à Claude et à ChatGPT par une adresse

      Le serveur MCP est servi à l'adresse https://mcp.sealtrust.io/mcp, sans compte et sans clef, en plus du paquet npm. Il porte les mêmes sept outils, avec les mêmes descriptions, et se colle tel quel comme connecteur personnalisé dans Claude ou en mode développeur dans ChatGPT.

      Détail

      La réponse que vous recevez maintenant. Le transport est le HTTP diffusable de la spécification MCP, sans session : chaque message est un POST indépendant, et un GET ou un DELETE répond 405. Une requête qui porte un en-tête Origin d'un autre site que Claude ou ChatGPT répond 403. Au-delà de 300 requêtes par minute et par adresse, ou de 120 appels d'outil par minute et par client, la réponse est 429 avec un en-tête Retry-After. Le détail est dans Claude et ChatGPT en 30 secondes.

      Ce que vous faites. Rien si vous utilisez le paquet npm : il ne change pas. Pour un assistant en ligne, collez l'adresse au lieu d'installer quoi que ce soit.

    4. Changement

      #Le catalogue par clef relit les lots, liste les passeports, prévisualise et donne les gabarits

      Cinq points d'entrée s'ajoutent à l'API du catalogue, tous avec le droit catalog:read :

      Détail
      • GET /partner/catalog/brands/{brand_code}/batches, un lot avec ?batch_code= ou la liste des lots ;
      • GET /partner/catalog/brands/{brand_code}/passports/list, la dernière version de chaque passeport de modèle et de lot, sans son contenu ;
      • POST /partner/catalog/brands/{brand_code}/passports/preview, ce que chaque public verra d'un brouillon, sans rien écrire ;
      • GET /partner/catalog/brands/{brand_code}/templates et GET /partner/catalog/brands/{brand_code}/templates/{template_key}, les gabarits de passeport.

      L'écriture du passeport brouillon accepte un champ de plus, only_if_draft. À true, elle ne complète que le brouillon ouvert : une dernière version scellée ou publiée est refusée en 409, et aucune version du tout en 404.

      La réponse que vous recevez maintenant. Les mêmes réponses qu'avant sur les points d'entrée existants. Sans only_if_draft, un envoi sur une version scellée ouvre toujours la version suivante.

      Ce que vous faites. Rien. Le détail est sur la page Remplir le catalogue par l'API.

  3. #

    1. Changement

      #La liste des produits du propriétaire montre la photo du modèle

      GET /my-products rendait media: [] pour une pièce frappée à partir d'un modèle quand la photo de ce modèle n'existe que comme image de couverture, sans média rattaché. La page publique du même produit montrait pourtant cette photo.

      Détail

      La réponse que vous recevez maintenant. Quand la liste media d'une pièce ne contient aucune image, l'image de couverture du modèle y est ajoutée en premier, avec l'identifiant 0, media_type à image et l'adresse publique de la photo. Une pièce qui a sa propre photo garde exactement sa liste.

      Ce que vous faites. Rien. Si vous preniez la première image de media comme vignette, elle apparaît désormais. Ne cherchez pas l'entrée d'identifiant 0 dans la médiathèque de la marque : elle n'a pas d'enregistrement propre.

    2. Changement

      #Revendiquer par numéro de série une pièce qu'on possède déjà

      POST /claims/from-serial répondait 409 « Already owned » ou « Already claimed » au propriétaire lui-même, comme à un inconnu.

      Détail

      La réponse que vous recevez maintenant. Quand le compte appelant est le détenteur enregistré de la pièce, la réponse est 409 avec detail « ALREADY_YOURS: you already own this product ». Tout autre compte reçoit exactement la réponse d'avant : la pièce a un propriétaire, jamais lequel.

      Ce que vous faites. Lisez le code avant les deux points. Sur ALREADY_YOURS, dites à la personne que le produit est déjà dans son espace, sans l'envoyer vers la marque.

    3. Changement

      #Le justificatif signé d'un modèle ou d'un lot a son adresse

      Le justificatif signé du passeport d'un modèle se lit à GET /passport/01/{gtin}/vc, et celui du passeport d'un lot à GET /passport/01/{gtin}/10/{lot}/vc. Chacun a aussi sa forme /vc/verify et sa forme /vc/preview, avec les mêmes niveaux d'accès que l'adresse d'un exemplaire. Jusqu'ici, ces justificatifs étaient signés à la publication mais ne se lisaient qu'à travers l'un des exemplaires du modèle : ceux d'une marque à l'offre Conformité, qui n'enregistre aucun exemplaire, ne se lisaient pas.

      Détail

      La réponse que vous recevez maintenant. Ces adresses répondaient 404. Elles répondent maintenant comme GET /passport/{identifier}/vc et ses deux voisines, avec en plus level (model ou batch), gtin et, pour un lot, batch_code.

      Ce que vous faites. Rien. Pour vérifier le passeport d'un modèle ou d'un lot, appelez la nouvelle adresse.

  4. #

    1. Changement

      #La taille, la couleur et le poids d'un passeport sont publics

      Les champs size, color et weight écrits à la racine d'un passeport sont maintenant rendus au niveau public, sur GET /passport/{identifier}, sur la copie publique et dans le justificatif signé. Ils étaient réservés aux autorités. Une marque qui a ses propres règles d'accès garde son choix.

      Détail

      La réponse que vous recevez maintenant. Ces trois champs, quand le passeport les porte, dans la réponse publique.

      Ce que vous faites. Rien.

    2. Changement

      #Le justificatif signé divulgue ce que montre la page

      GET /passport/{identifier}/vc et sa présentation par niveau suivent maintenant exactement les règles de GET /passport/{identifier} : le groupe de produits est lu dans le passeport, donc les mentions d'étiquette d'un textile (composition en fibres) et d'une chaussure y sont publiques, et l'annexe XIII d'une batterie y ouvre et y ferme les mêmes champs. Une liste est divulguée ligne par ligne et champ par champ, comme la page la découpe.

      Détail

      La réponse que vous recevez maintenant. Au niveau public, le justificatif d'un textile porte composition avec material et percentage de chaque ligne. Il ne les portait pas. Celui d'une batterie ne porte plus les références de pièces, l'état de la batterie ni la durée de vie en cycles.

      Ce que vous faites. Rien. Un justificatif déjà émis garde ses divulgations ; il est réémis à la prochaine publication du passeport.

    3. Rupture de compatibilité

      #un numéro de marque s'écrit en chiffres seuls

      Un numéro de marque (brand_id, parent_brand_id) est maintenant lu d'une seule façon partout : un entier JSON, ou une chaîne faite uniquement de chiffres. Un code de marque (brand_code) est un code public de dix caractères, ou, pendant la transition, des chiffres. Avant, l'API convertissait d'autres écritures sans le dire : 5.0, "+5", " 5", "5.0", "0_5" et true, ce dernier lu comme la marque 1. La règle vaut pour le chemin, les paramètres de l'adresse, le corps et chaque ligne d'un lot, sur toutes les routes.

      Détail

      La réponse que vous recevez maintenant. Une valeur mal écrite reçoit un 422 dont detail.code vaut BRAND_REF_NOT_WELL_FORMED. detail.references dit où se trouve la valeur, sans la répéter. Dans un lot envoyé en CSV, la ligne fautive est signalée dans le refus 400 habituel, et son message commence par BRAND_REF_NOT_WELL_FORMED. Le lot entier est refusé et rien n'est créé.

      Ce que vous faites. Envoyez le numéro en chiffres seuls, 5 ou "5", ou mieux, le brand_code que donne GET /v1/partner/catalog/brands. Si votre code produit un nombre décimal, convertissez-le en entier avant l'envoi.

    4. Changement

      #Les codes d'achat s'émettent en masse par l'API

      Une clef qui porte le nouveau droit claim_codes:issue émet les codes d'achat de la règle « code d'achat exigé » pour une liste de pièces, 500 au plus par appel : POST /partner/claim-codes/brands/{brand_code}/issue. Elle lit leur état, jamais le code, avec POST /partner/claim-codes/brands/{brand_code}/status. POST /partner/mint/batch?issue_purchase_codes=true demande un code par pièce au moment de la frappe, et GET /partner/mint/batch/{job_id}/items les donne une seule fois, dans purchase_code.

      Détail

      La réponse que vous recevez maintenant. Chaque code n'apparaît qu'une fois. Une pièce dont le code a déjà servi reçoit un 409 PURCHASE_CODE_ALREADY_USED, sauf avec "replace_used": true. Une pièce d'une autre marque reçoit le même 404 PIECE_NOT_FOUND qu'une pièce inconnue. La réponse de POST /partner/mint/batch porte un sixième champ, issue_purchase_codes.

      Ce que vous faites. Rien si vous n'utilisez pas les codes d'achat. Sinon, cochez claim_codes:issue sur une clef dédiée, jamais coché par défaut, et lisez Émettre les codes d'achat.

    5. Changement

      #La vérification d'un QR signé ne dit plus « authentique »

      GET /qr/verify ne répond plus « Authentic product: QR code and blockchain verified. ». Un QR signé prouve que la marque a enregistré ce numéro et que la chaîne le confirme, pas que l'objet en main est l'original : un QR code se photocopie. Le refus ne dit plus « Invalid product » : c'est le code qui ne correspond pas, rien n'est connu du produit lui-même.

      Détail

      La réponse que vous recevez maintenant. Le même champ valid. message vaut « Registered number: the QR signature and the on-chain record match. A QR code can be copied: it does not prove the item in hand is the original. » ou « Unrecognised code: the QR signature or the on-chain record does not match. ».

      Ce que vous faites. Rien si vous lisez valid, comme cette page le demande. Si vous affichiez message, n'affichez jamais « authentique » à partir de cette réponse.

    6. Changement

      #La lecture publique d'un produit donne sa date d'enregistrement et l'état de son certificat

      GET /products/{uid}/public rend deux champs de plus. registered_at porte la date de création de l'enregistrement du produit, sous un nom qui dit ce qu'elle est. certificate_status porte l'état du certificat d'authenticité en vigueur, lu exactement comme GET /resolve/{identifier} le lit : active, ou null sans certificat en vigueur. verified_at reste, avec la même valeur que registered_at.

      Détail

      La réponse que vous recevez maintenant. Seize champs au lieu de quatorze. Rien d'autre ne change.

      Ce que vous faites. Rien d'obligatoire. Si vous affichiez verified_at comme une date de vérification, lisez registered_at et écrivez « enregistré le » : personne n'a vérifié l'objet à cette date.

    7. Changement

      #Le certificat en PDF ne dit plus « authentique, vérifié blockchain »

      Le sceau en filigrane de GET /certificate/{identifier}/download dit maintenant ENREGISTRÉ et SUR UNE BLOCKCHAIN PUBLIQUE (en anglais REGISTERED et ON A PUBLIC BLOCKCHAIN). Il disait AUTHENTIQUE et VÉRIFIÉ BLOCKCHAIN. Une chaîne publique prouve qu'un enregistrement a été fait, pas que l'objet en main est l'original. Le pied de page par défaut suit la même règle. Un certificat révoqué ou expiré n'a plus de sceau : il portait la même coche qu'un certificat valide.

      Détail

      La réponse que vous recevez maintenant. Un PDF dont le sceau et le pied de page ont changé de texte. Le reste du document est identique.

      Ce que vous faites. Rien, sauf si vous comparez le texte du PDF à une valeur attendue.

  5. #

    1. Changement

      #Une marque se nomme par son code public dans l'API partenaire

      Chaque marque a un code public de dix caractères, brand_code, par exemple 7K3QXW9M2A. L'API partenaire nomme maintenant la marque par ce code : dans le chemin des routes /partner/catalog/brands/{brand_code}/..., dans le corps de POST /partner/sellout et de POST /partner/webhooks, dans la liste GET /partner/webhooks?brand_code=, et sur chaque ligne de POST /partner/mint/batch. GET /partner/catalog/brands donne le code de chaque marque pour laquelle votre clef peut agir. Les routes publiques GET /brands/{brand_code}/branding et GET /brand/{brand_code}/pubkey acceptent aussi le code.

      Détail

      La réponse que vous recevez maintenant. Les réponses de l'API partenaire, du portail partenaire, de /sdm/verify et des clefs publiques qui rendaient brand_id rendent aussi brand_code. brand_id y est déprécié. Un code qui ne nomme aucune marque reçoit le même 403 qu'une marque que la clef ne sert pas. Une requête qui envoie brand_code et brand_id ensemble est refusée : en 422 pour une vente ou un abonnement, en 400 pour une ligne de lot.

      Ce que vous faites. Rien dans l'immédiat : le numéro brand_id est encore accepté partout où il l'était. Remplacez-le par brand_code à votre prochaine évolution. GET /brand/{brand_id}/did.json garde le numéro, parce qu'il fait partie de l'identifiant did:web déjà signé dans les justificatifs émis.

    2. Changement

      #L'adresse à coller dans votre ERP nomme la marque par son code public

      Shopify, WooCommerce, HubSpot et Akeneo envoient leurs événements à l'adresse /webhooks/integrations/{provider}/{brand_code}. La console, dans Réglages > Intégrations, affiche maintenant cette adresse avec le code public de la marque, sur la carte de chaque connecteur et dans l'assistant de création.

      Détail

      La réponse que vous recevez maintenant. Une adresse dont le code ne nomme aucune marque reçoit le même 404 qu'une marque sans connecteur actif. L'adresse qui porte le numéro de la marque continue de fonctionner.

      Ce que vous faites. Rien : une adresse déjà collée dans votre ERP continue de fonctionner. Pour un nouveau connecteur, collez l'adresse que la console affiche.

    3. Changement

      #L'espace du propriétaire donne le code public de la marque

      Les réponses de l'espace du propriétaire qui rendaient brand_id rendent aussi brand_code : GET /my-products, son historique, la liste des marques GET /my-products/products/brands, les routes /ownership/..., les rachats, les retours, les messages et les codes de remise /custody/repair-codes. L'export GET /my-products/export/csv gagne une colonne brand_code, placée juste après brand_id.

      Détail

      La réponse que vous recevez maintenant. brand_id y est déprécié. GET /my-products accepte le filtre ?brand_code=.

      Ce que vous faites. Rien dans l'immédiat : brand_id est encore rendu. Lisez brand_code à votre prochaine évolution. Si vous lisez l'export par la position des colonnes, décalez de un toutes les colonnes qui suivent brand_id.

    4. Changement

      #La composition d'un textile et les matières d'une chaussure sont publiques

      data au niveau public de GET /passport/{identifier}, GET /passport/01/{gtin}, GET /passport/01/{gtin}/10/{lot} et GET /resolve/{identifier} porte maintenant, pour un passeport textile, le nom et le pourcentage de chaque matière (materials.<ligne>.name et materials.<ligne>.percentage), et pour un passeport chaussure le nom de la matière de la tige, de la doublure, de la première de propreté et de la semelle. Ce sont des mentions d'étiquette : règlement (UE) n° 1007/2011 pour la composition en fibres, directive 94/11/CE pour la chaussure. Elles restent publiques quelles que soient les règles d'accès de la marque.

      Détail

      La réponse que vous recevez maintenant. Un bloc materials peut apparaître dans data au niveau public. L'origine des matières n'en fait pas partie.

      Ce que vous faites. Rien. Si vous affichiez « composition réservée » faute de materials, affichez le bloc quand il est présent.

    5. Changement

      #Un bloc de matières vide n'est plus noté comme une composition déclarée

      Le bloc evidence de GET /passport/{identifier}, de GET /passport/01/{gtin}, de GET /passport/01/{gtin}/10/{lot} et de GET /resolve/{identifier} notait composition: "declared" dès que le passeport portait un bloc materials, même quand toutes ses lignes étaient vides : un gabarit enregistré tel quel suffisait.

      Détail

      La réponse que vous recevez maintenant. composition n'apparaît que si une ligne de matière porte au moins un nom, une part ou une origine. Sinon la clef est absente de sections, comme pour un passeport sans matières. Un chiffre laissé vide par la marque n'est jamais rendu comme 0.

      Ce que vous faites. Rien. Traitez l'absence de composition comme vous le faites déjà : la marque n'a rien déclaré.

    6. Changement

      #La photo du produit est servie en copie allégée

      image_url des passeports (GET /passport/{identifier}, GET /passport/01/{gtin}, GET /passport/01/{gtin}/10/{lot} et les ensembles partenaires correspondants), de GET /products/{uid}/public, de GET /qr/verify et de GET /timeline/{identifier}, ainsi que url des images de media dans GET /resolve/{identifier}, désignent maintenant une copie allégée de la photo au lieu du fichier d'origine.

      Détail

      La réponse que vous recevez maintenant. Une adresse qui se termine par .photo.webp : une image WebP de 800 pixels au plus sur le grand côté, de 100 Ko au plus, sans données EXIF. Une photo qui n'a pas encore sa copie garde l'adresse de l'original ; une adresse d'un autre site est rendue telle quelle. Le champ ne change ni de nom ni de type, et null garde le même sens.

      Ce que vous faites. Rien. Si votre intégration déduisait le format du fichier de l'extension de l'adresse, lisez l'en-tête Content-Type de la réponse à la place.

    7. Changement

      #Aucune offre ne refuse plus une pièce pour un volume mensuel

      Les quatre offres comptent maintenant les pièces par année civile. Les 6 000 premières de l'année sont comprises, chaque pièce au-delà est facturée, et au-delà d'un million de pièces dans l'année le prix se fixe sur devis. Aucune pièce n'est refusée pour une question de volume. Cela vaut sur l'offre Conformité + Identité. Seul le compte d'essai garde un volume mensuel.

      Détail

      La réponse que vous recevez maintenant. Sur une offre payante, un lot envoyé à POST /v1/partner/mint/batch ne reçoit plus le 403 dont detail.code vaut QUOTA_EXCEEDED et detail.resource vaut products. Il est accepté. Un compte d'essai reçoit toujours ce refus, avec "period": "monthly".

      Ce que vous faites. Rien. Si votre intégration attendait ce refus pour étaler ses envois d'un mois sur l'autre, vous pouvez retirer cette attente.

  6. #

    1. Changement

      #La clef revendeur frappe, vend et gère les webhooks de ses marques clientes

      Une clef revendeur agit maintenant pour chacune de vos marques clientes sur tous les points d'entrée, sans clef par marque : la frappe (POST /v1/partner/mint/batch, avec le suivi du lot et ses pièces), la déclaration de vente (POST /v1/partner/sellout) et les webhooks (/v1/partner/webhooks), en plus du catalogue et de l'affichage du passeport.

      Détail

      La réponse que vous recevez maintenant. Un lot dont les lignes portent le brand_id d'une de vos marques clientes est accepté. La vente et l'abonnement prennent un champ brand_id facultatif, et la liste des abonnements un paramètre brand_id. Une marque que la clef ne sert pas reçoit toujours un 403, dont detail vaut désormais "This API key cannot act for this brand." (le texte "Brand mismatch: ..." disparaît). Un lot qui mélange plusieurs marques reçoit un 422 dont detail.code vaut ONE_BRAND_PER_BATCH.

      Ce que vous faites. Rien si vous ne lisiez pas le texte du refus. Si votre intégration le comparait à "Brand mismatch", comparez le code 403 à la place. Avec une clef revendeur, vous pouvez retirer les clefs créées sur vos marques clientes : le détail est sur la page Remplir le catalogue par l'API.

    2. Changement

      #Le passeport d'un modèle ou d'un lot porte la photographie du produit

      GET /passport/01/{gtin} et GET /passport/01/{gtin}/10/{lot} ajoutent le champ image_url, que le passeport d'une pièce portait déjà. Les passeports groupés GET /v1/partner/passports/01/{gtin} et GET /v1/partner/passports/01/{gtin}/10/{lot} le portent aussi, dans passport.

      Détail

      La réponse que vous recevez maintenant. image_url est l'adresse complète de la photographie du modèle, calculée de la même façon aux trois niveaux : sa photo principale, ou à défaut la première image de sa médiathèque. null quand le modèle n'en a pas. Sur le passeport d'une pièce, trois cas changent : une photo enregistrée comme simple chemin de stockage arrive maintenant en adresse complète, un modèle sans photo principale montre la première image de sa médiathèque au lieu de null, et un modèle d'une autre marque que celle du passeport répond null.

      Ce que vous faites. Rien : c'est un champ en plus. Affichez-le si votre page montre le produit.

  7. #

    1. Changement

      #Deux offres par marque : Conformité, ou Conformité + Identité

      Chaque marque est désormais sur l'offre compliance ou sur l'offre compliance_identity. Toutes les marques existantes sont sur compliance_identity, qui est ce qu'elles avaient déjà : rien ne change pour elles. L'offre compliance couvre le passeport produit par modèle ou par lot, sans identité à la pièce.

      Détail

      La réponse que vous recevez maintenant. Pour une marque sur l'offre compliance, toute opération sur une pièce répond 403, avec detail.code égal à OFFER_EXCLUDES_IDENTITY et detail.feature qui dit ce qui a été refusé : création de pièces par POST /v1/partner/mint/batch, lecture de puce par POST /v1/partner/nfc/verify, déclaration de vente par POST /v1/partner/sellout, et les mêmes opérations dans la console. Rien n'est écrit, mis en file ou envoyé à la blockchain.

      Ce que vous faites. Rien pour une marque qui ne change pas d'offre. Pour une marque sur l'offre compliance, ne rejouez pas un appel refusé avec ce code, et imprimez le QR code de son modèle ou de son lot. Le détail est sur la page Conformité, ou Conformité + Identité.

    2. Changement

      #Le QR code d'un modèle ou d'un lot revient pour l'offre Conformité

      GET /qr/01/{gtin}, GET /qr/01/{gtin}/download, GET /qr/01/{gtin}/10/{lot} et GET /qr/01/{gtin}/10/{lot}/download dessinent de nouveau le code du modèle ou du lot, pour une marque sur l'offre compliance dont ce passeport est publié.

      Détail

      La réponse que vous recevez maintenant. 200 et une image PNG pour une telle marque. Dans tous les autres cas, la même réponse qu'avant : 410 avec detail.code égal à ITEM_QR_ONLY.

      Ce que vous faites. Rien si vos marques sont sur l'offre compliance_identity : elles impriment toujours le code de chaque pièce.

    3. Changement

      #Un QR code à imprimer porte le domaine de la marque une fois ce domaine définitif

      GET /qr/product/{identifier}, GET /qr/product/{identifier}/download, GET /qr/verify/{identifier}, GET /qr/certificate/{identifier} et GET /qr/certificate/{identifier}/download, ainsi que le QR code du PDF de certificat et la carte de portefeuille, portent le domaine de la marque seulement quand ce domaine est définitif (verrouillé). Avant, ils portent notre adresse. Un domaine seulement vérifié peut encore changer, et un code imprimé qui le portait ne mènerait plus nulle part après ce changement.

      Détail

      La réponse que vous recevez maintenant. Le code reste 200 et l'image reste un PNG. Pour une marque dont le domaine est vérifié mais pas encore définitif, l'adresse encodée commence par https://sealtrust.io au lieu du domaine de la marque. Cette adresse reste valable pour toujours.

      Ce que vous faites. Rien. Si vous imprimez ces codes pour une marque qui veut son domaine sur ses étiquettes, la marque rend d'abord son domaine définitif depuis sa console, par l'export des QR codes d'un lot.

  8. #

    1. Changement

      #Le passeport dit les niveaux qui vous sont ouverts

      GET /passport/{identifier}, GET /passport/01/{gtin} et GET /passport/01/{gtin}/10/{lot} ajoutent le champ available_tiers : les niveaux d'accès que votre appel peut demander sur ce passeport, dans l'ordre public, end_user, repairer, recycler, upstream, authority. Les passeports de modèle et de lot ajoutent aussi access_tier, le niveau servi, qui n'était jusque-là que dans l'en-tête X-DPP-Access-Tier.

      Détail

      La réponse que vous recevez maintenant. Le code reste 200. Sans authentification, available_tiers vaut ["public"]. La liste ne donne aucun droit : chaque niveau demandé reste vérifié, avec les mêmes refus qu'avant.

      Ce que vous faites. Rien. Si vous proposez un choix de niveau à vos utilisateurs, proposez les niveaux de cette liste, et seulement eux. Une réponse faite pour un compte connecté ne doit aller dans aucun cache partagé.

    2. Rupture de compatibilité

      #GET /resolve/{identifier} sert le niveau public

      GET /resolve/{identifier} ne lit aucune session. Il servait pourtant le passeport au niveau end_user, à tout appelant. Il sert désormais le niveau public, et lui seul : les mêmes données que GET /passport/{identifier} sans compte. Un appel fait avec une session, celle du propriétaire comprise, reçoit la même réponse.

      Détail

      La réponse que vous recevez maintenant. Le code reste 200. Dans passport.data, l'impact environnemental, l'indice de réparabilité, la durée de vie, la durabilité, l'efficacité énergétique, l'empreinte carbone, la matière principale, l'entretien et le mouvement n'apparaissent plus. Le taux de recyclabilité et le contenu recyclé restent. Le bloc evidence ne change pas.

      Ce que vous faites. Si votre intégration affiche ces champs, lisez-les sur GET /passport/{identifier} avec un jeton de session de compte dans Authorization: Bearer. Si vous les affichez quand ils manquent, affichez une absence, jamais un zéro : une valeur absente n'est pas une valeur nulle.

    3. Rupture de compatibilité

      #Le niveau d'accès end_user demande un compte connecté

      Le niveau end_user ajoute au passeport l'impact environnemental, la circularité, la matière principale, la durabilité et l'efficacité énergétique. Il était servi à n'importe quel appelant qui le demandait par ?access_tier=end_user, sans session. Il est désormais réservé à un compte connecté. Le propriétaire actuel de l'exemplaire le reçoit toujours par défaut.

      Détail

      La règle vaut pour les six points d'entrée qui prennent access_tier : GET /passport/{identifier}, GET /passport/01/{gtin}, GET /passport/{identifier}/vc, GET /passport/{identifier}/vc/preview, GET /passport/{identifier}/vc/verify et GET /passport/01/{gtin}/10/{lot}.

      La réponse que vous recevez maintenant. Un appel sans session au niveau end_user reçoit 401, avec detail égal à End-user-tier access requires authentication. Le niveau public ne change pas.

      GET /resolve/{identifier} ne prend pas de paramètre access_tier. Il sert le niveau public, voir l'entrée précédente.

      Ce que vous faites. Si votre intégration demande end_user sans session, présentez un jeton de session de compte dans Authorization: Bearer, ou demandez le niveau public. Une clef d'API partenaire n'est pas une session et n'ouvre pas ce niveau.

    4. Rupture de compatibilité

      #Le portail partenaire répond 404 pour le produit d'une autre marque

      GET /v1/partner-portal/products/{identifier} et POST /v1/partner-portal/interventions répondaient 403, avec le message Ce produit appartient à une marque qui ne vous a pas accrédité, quand l'identifiant était un numéro de jeton, une empreinte ou un numéro de certificat d'une marque qui ne vous a pas accrédité. Par numéro de série, la même demande répondait déjà 404. La réponse 403 confirmait que le produit existe.

      Détail

      La réponse exacte. Ces demandes répondent maintenant 404, detail vaut Produit introuvable, exactement comme pour un identifiant inventé. Sur le domaine propre d'une marque, le portail ne trouve que les produits de cette marque. Le 403 Aucune accréditation active ne change pas.

      Ce que vous faites. Si votre code traite ce 403 à part, traitez le 404 à la place : le produit n'est pas dans votre périmètre, ou il n'existe pas.

    5. Rupture de compatibilité

      #Les niveaux de métier sont réservés aux administrateurs de la marque

      Les niveaux repairer, recycler et upstream étaient ouverts à tout compte rattaché à la marque du produit, même quand ce compte n'était pas administrateur : un ancien administrateur redevenu client, ou devenu réparateur, lisait encore les trois niveaux. Ils sont ouverts maintenant aux administrateurs de la marque, aux partenaires pour le niveau de leur accréditation, et à l'autorité de surveillance du marché.

      Détail

      La réponse exacte. Un tel compte reçoit 403 sur GET /v1/passport/{identifier}, GET /v1/passport/01/{gtin}, GET /v1/passport/01/{gtin}/10/{lot} et sur les trois points d'entrée d'identité vérifiable, avec le detail habituel de ce refus. Un réparateur garde le niveau repairer, et un recycleur le niveau recycler.

      Ce que vous faites. Rien si vous appelez avec un compte administrateur ou partenaire. Sinon, demandez à la marque le rôle qui correspond à votre usage.

    6. Changement

      #Afficher le passeport dans votre propre page

      Six points d'entrée s'ajoutent pour le partenaire qui affiche les passeports d'une marque dans sa propre page. Trois droits nouveaux les ouvrent : passport:read, scans:write et nfc:verify.

      Détail
      • GET /partner/passports/{identifier}, GET /partner/passports/01/{gtin} et GET /partner/passports/01/{gtin}/10/{lot} rendent le passeport, sa preuve et sa signature en un appel.
      • POST /partner/scans et POST /partner/nfc/verify comptent chaque scan pour le visiteur nommé par quatre en-têtes signés, et plus pour votre serveur.
      • GET /partner/nfc/receipts/{receipt} lit une lecture de puce que notre page a déjà vérifiée : la puce n'est vérifiée qu'une fois.
      • POST /partner/nfc/verify répond 503 NFC_READ_IN_PROGRESS, avec Retry-After, à une relance qui arrive pendant que votre premier appel vérifie encore la même lecture, au lieu de la prendre pour une copie.
      • Nos pages /claim et /transfer acceptent serial et return et renvoient le visiteur vers votre page, avec claim_outcome ou transfer_outcome. GET /my-products porte désormais le champ serial.

      Une clef revendeur agit aussi pour les marques clientes sur ces six points d'entrée.

      Quand une marque l'active dans la console, /p/{serial}, /01/{gtin}/21/{serial}, /01/{gtin} et /01/{gtin}/10/{lot} répondent 302 vers la page du partenaire, et GET /sdm/verify-url porte un champ partner_redirect_url. Sans ce réglage, ces réponses ne changent pas, et le champ vaut null.

      Ce que vous faites. Rien pour une intégration existante. Pour afficher les passeports dans votre page, lisez Afficher le passeport dans votre page.

    7. Changement

      #Un passeport par lot de production

      Un passeport peut désormais être rattaché à un lot de production, en plus du modèle et de l'exemplaire. Trois adresses publiques nouvelles, sans clef :

      Détail
      • GET /01/{gtin}/10/{lot}, le lien GS1 du lot, qui répond 302 vers la page du passeport du lot, ou vers celle du modèle quand le lot n'a pas encore de passeport publié ;
      • GET /v1/passport/01/{gtin}/10/{lot}, le passeport du lot, avec "level": "batch", product_batch_id et batch_code ;
      • GET /v1/passport/01/{gtin}/10/{lot}/proof, ses preuves. Son ancrage sur la chaîne vient du même balayage que les autres passeports, sans aucune frappe.

      Un exemplaire sans passeport propre reçoit maintenant celui de son lot quand il en a un, avant celui de son modèle, sur GET /v1/passport/{identifier}, sa preuve, sa vérification, GET /v1/resolve/{identifier} et le portail partenaire.

      Ce que vous faites. Rien : aucune réponse existante ne change tant qu'aucun passeport de lot n'est publié. Si votre intégration lit product_id: null comme « passeport du modèle », lisez plutôt le champ level des réponses de niveau partagé, qui vaut model ou batch.

      Trois règles protègent les étiquettes de lot déjà imprimées :

      • le GTIN d'un modèle dont un lot a un passeport publié ne change plus : PUT /admin/product-models/{id} répond 409, et l'import Excel refuse la ligne ;
      • un GTIN n'appartient qu'à une marque, à tous les niveaux : publier un passeport de modèle ou de lot sous un GTIN qu'une autre marque publie déjà, par son modèle ou par l'un de ses lots, répond 409 ;
      • publier un brouillon de lot dont le lot a été renommé depuis répond 422.

      L'archive de réversibilité écrit pour chaque passeport son level, son product_batch_id et son batch_code.

    8. Changement

      #Remplir le catalogue par l'API, et une clef pour plusieurs marques

      Sept points d'entrée s'ajoutent sous /partner/catalog. Ils créent ou complètent des modèles, des lots et le passeport brouillon d'un modèle, et ils lisent le score de complétude. Deux droits nouveaux les ouvrent : catalog:read et catalog:write. Chaque écriture se rejoue sans doublon, et un champ absent ou à null n'efface rien. Rien n'est publié par cette voie. Chaque écriture est inscrite au journal d'audit, avec la clef, la marque visée et chaque champ modifié avant et après. Chaque appel, lecture comprise, est inscrit au journal d'utilisation de la clef. Un modèle créé par cette voie est au niveau unit, comme par tout autre chemin.

      Détail

      Une clef revendeur agit aussi pour les marques clientes rattachées au contrat revendeur, sur ces sept points d'entrée seulement.

      Chaque lot rendu porte lot_passport_blocker : null quand son code peut porter un passeport de lot, sinon la raison (plus de 20 caractères, ou un caractère que le lien GS1 refuse). Le lot est créé quand même.

      Quatre droits qui n'ouvraient aucun point d'entrée ne se proposent plus à la création d'une clef : mint:single, products:read, products:status et transfers:create. Une clef qui en porte déjà un continue de fonctionner exactement comme avant, puisque ce droit n'ouvrait rien.

      Ce que vous faites. Rien pour une intégration existante. Pour préremplir votre catalogue, créez une clef portant catalog:write et lisez Remplir le catalogue par l'API.

    9. Rupture de compatibilité

      #Seul le QR code d'un exemplaire se génère

      Le code à imprimer est celui de chaque exemplaire, /p/{serial}. Un QR code partagé par toutes les pièces d'un modèle ou d'un lot ne se génère plus.

      Détail

      La réponse que vous recevez maintenant. GET /qr/01/{gtin}, GET /qr/01/{gtin}/download, GET /qr/01/{gtin}/10/{lot} et GET /qr/01/{gtin}/10/{lot}/download répondent 410, avec detail.code égal à ITEM_QR_ONLY. La réponse est la même pour tout GTIN et tout lot, connus ou non. Les liens /01/{gtin} et /01/{gtin}/10/{lot} eux-mêmes ne changent pas : un code déjà imprimé mène toujours à son passeport.

      Ce que vous faites. Dessinez le code de chaque exemplaire avec GET /qr/product/{serial}/download. La page de chaque exemplaire affiche le passeport de son lot, sinon celui de son modèle.

    10. Changement

      #Un exemplaire par pièce

      Un modèle de produit porte le champ identity_level, qui vaut unit : chaque pièce reçoit son propre jeton sur la chaîne et son propre QR code, pour l'authentification, le propriétaire, la revente et la déclaration de vol. Le passeport reste porté par le modèle ou par le lot, et chaque exemplaire en hérite.

      Détail
      • POST /admin/product-models et PUT /partner/catalog/brands/{brand_id}/models créent un modèle au niveau unit, que le corps donne le champ ou non. Les connecteurs et l'import Excel créent aussi au niveau unit. Envoyer "identity_level": "model" répond 422.
      • Un modèle encore au niveau model ne reçoit aucun exemplaire : /mint, /mint/batch, /admin/batch/mint, /admin/batch/prepare et /admin/batch/prepare-mapped répondent 409 avec le code MODEL_LEVEL_IDENTITY, avant toute écriture, que la ligne nomme le modèle ou seulement son lot. Le détail ne donne que des identifiants.
      • Une ligne qui nomme le modèle ou le lot d'une autre marque que son brand_id répond 404 avec le code UNKNOWN_PRODUCT_REFERENCE, comme un identifiant qui n'existe pas.

      Ce que vous faites. N'envoyez pas identity_level, ou envoyez "unit". Si un 409 MODEL_LEVEL_IDENTITY vous répond, passez le modèle au niveau unit par PUT /admin/product-models/{id}, puis renvoyez la même demande : un nouvel essai sans ce changement reçoit la même réponse.

  9. #

    1. Changement

      #Le score de conformité d'une batterie se mesure sur la liste de la Commission

      La dimension « Conformité » du score de préparation ne vérifiait que trois choses : que le passeport existe, qu'il est publié, et que les champs requis de notre gabarit sont remplis. Pour une batterie, elle compare désormais aussi votre passeport aux données numérotées du guide de la Commission, et nomme celles qui manquent par leur numéro officiel.

      Détail

      L'exigence dépend de la catégorie : 46 données obligatoires pour un véhicule électrique, 49 pour un moyen de transport léger, 32 pour une batterie industrielle. Le score lit product_identity.battery_category et note sur la colonne correspondante.

      Ce que vous faites. Le score d'un modèle batterie peut baisser, parce qu'il mesure maintenant ce que le règlement demande et non plus seulement ce que notre gabarit propose. Le cockpit liste les données manquantes avec leur numéro : c'est la même numérotation qu'une autorité emploiera.

      ⚠️ Quelques données obligatoires n'ont pas encore de champ chez nous. Elles ne sont ni comptées remplies ni comptées manquantes : le cockpit les affiche à part, comme un manque de notre côté.

    2. Changement

      #La chronologie d'un produit ne montre ses adresses qu'à sa marque

      GET /v1/timeline/{identifier} rendait les adresses e-mail et les adresses de portefeuille en clair à n'importe quel compte administrateur connecté, quelle que soit sa marque. Le clair est désormais réservé. Les adresses e-mail en clair ne vont qu'au propriétaire actuel du produit et à un administrateur de la marque qui a émis ce produit ; les adresses de portefeuille from_address et to_address ne vont qu'à cet administrateur de marque. Tout autre appelant, connecté ou non, reçoit la projection anonymisée : les adresses e-mail des autres parties y sont masquées, from_address et to_address valent null. Un superadmin SealTrust reçoit le clair sur tous les produits, parce que c'est le compte d'exploitation.

      Détail

      Ce que vous faites. Rien, si vous appeliez cette adresse pour vos propres produits. Si votre intégration lisait la chronologie de produits d'une autre marque, elle recevra désormais la projection anonymisée.

  10. #

    1. Changement

      #La résolution dit sur quelle base croire chaque section du passeport

      GET /v1/resolve/{identifier} rend un champ de plus, evidence. Il dit, pour chaque section notée du passeport, si elle est vérifiée mécaniquement contre un registre public, appuyée sur un document tiers joint, ou déclarée par la marque.

      Détail

      C'est le même bloc, calculé par la même fonction, que celui que rend déjà GET /v1/passport/{identifier}. La fonction est la même, la version de passeport lue peut différer : /resolve est public et ne lit que les versions en visibilité public, tandis que /passport sert en plus les versions en visibilité owner_only au propriétaire actuel qui s'authentifie. Une unité qui porte une version owner_only peut donc recevoir deux niveaux de preuve différents selon le point d'entrée appelé. Pour tout autre appelant, les deux réponses portent la même version et le même niveau.

      La réponse exacte. Le champ s'ajoute à la fin de l'objet, à côté de passport_anchor. Le bloc ci-dessous est celui d'un passeport qui décrit à la fois ses matières et ses substances préoccupantes.

      JSON
      {
        "evidence": {
          "sections": {
            "identity": "verified",
            "integrity": "verified",
            "composition": "declared",
            "substances_of_concern": "declared"
          },
          "legend": {
            "declared": "Stated by the brand. Recorded, dated and attributable, but not independently checked.",
            "verified": "Rests on an on-chain reference that anyone can re-check independently. SealTrust records it and does not re-query the chain on each request."
          },
          "derived": true,
          "note": "Statuses are computed by SealTrust from verifiable facts. A brand cannot set them."
        }
      }

      Il vaut null quand aucun passeport publié n'est servi, et null si son calcul échoue.

      Quatre sections peuvent être notées, et aucune autre partie du passeport ne reçoit de mention. identity et integrity sont toujours là, parce qu'elles portent sur l'exemplaire lui-même. composition et substances_of_concern n'apparaissent que si les données du passeport les portent : sections compte donc de deux à quatre entrées selon l'exemplaire. Lisez les clefs présentes, n'en supposez aucune.

      Ce que vous faites. Rien. Le champ s'ajoute, aucun champ ne change de sens et aucun appel qui passait avant ne casse. Un client qui ne lit pas evidence reçoit la même réponse qu'avant. Si vous décidez d'afficher ces mentions, n'en affichez que pour les sections que sections nomme, et ne recopiez pas ces valeurs dans votre base : elles sont recalculées à chaque lecture.

  11. #

    Trois changements. Le premier est une rupture de compatibilité, et il concerne tout partenaire accrédité.

    1. Rupture de compatibilité

      #Le niveau d'accès value_chain n'existe plus

      Le niveau value_chain était accordé à tout partenaire portant n'importe quelle accréditation sur la marque, et il rendait la réunion de ce que voient le réparateur, le recycleur et le fournisseur amont. Un recycleur accrédité y lisait donc les données de fabrication et la chaîne d'approvisionnement, que le niveau recycler lui refuse. Cette porte est fermée.

      Détail

      Six niveaux subsistent : public, end_user, repairer, recycler, upstream et authority. Les trois niveaux de métier sont des publics distincts : détenir une accréditation n'en ouvre aucune autre.

      La réponse exacte. ?access_tier=value_chain répond maintenant 422, sur GET /v1/passport/{identifier}, sur GET /v1/passport/01/{gtin} et sur les trois points d'entrée d'identité vérifiable. detail est la liste de validation habituelle, chaque entrée portant loc, type et msg, et loc nomme access_tier.

      Ce que vous faites. Demandez le niveau de votre métier, repairer ou recycler. Vous recevrez moins de champs qu'avant, et ce sont précisément les champs qui ne relèvent pas de votre métier.

    2. Changement

      #Le portail partenaire annonce le métier qu'il a servi

      Dans GET /v1/partner-portal/products/{identifier}, le champ passport.access_tier valait toujours value_chain. Il porte maintenant les métiers réellement servis, joints par un + et rangés par ordre alphabétique : recycler, ou repairer+recycler pour un compte qui porte les deux accréditations sur cette marque. Il vaut public quand la marque ne vous en a reconnu aucune, et le passeport est alors filtré au niveau public.

      Détail

      Ce que vous faites. Si votre code compare cette valeur à value_chain, reprenez la comparaison. Et attendez-vous à un contenu plus étroit qu'avant : il est désormais filtré sur vos seules accréditations.

    3. Changement

      #L'image publique d'un exemplaire remonte à son modèle

      Dans GET /v1/products/{uid_hash}/public, le champ image_url ne regardait que les médias attachés directement à l'exemplaire, et valait null dès qu'il n'en avait aucun. Il suit maintenant la même chaîne de repli que le reste du site : une image de l'exemplaire, puis l'image principale de son modèle, puis les médias du modèle.

      Détail

      Ce que vous faites. Rien. Un champ qui valait null peut désormais porter une adresse.

  12. #

    Deux corrections d'API. Aucune n'est une rupture de compatibilité, mais toutes deux annulent un conseil que cette documentation donnait auparavant.

    1. Changement

      #Le suivi d'un lot dit enfin qu'un lot a échoué

      Un lot dont aucune ligne n'aboutissait répondait status: "finished", parce que c'était la file d'exécution qui parlait. La même adresse basculait ensuite sur failed une fois la rétention de la file écoulée, environ huit minutes plus tard. Même lot, même exécution, deux réponses opposées selon l'heure du regard.

      Détail

      Désormais, dès que votre réservation porte un état terminal, c'est elle qui décide : un lot dont aucune ligne n'a abouti répond status: "failed". Et les quatre compteurs batch_status, items_count, success_count et error_count sont présents dans toute réponse, dès le premier appel, alors qu'ils n'apparaissaient qu'une fois le lot oublié par la file.

      Ce que vous faites. Fondez votre décision sur success_count et error_count. Si vous aviez suivi notre ancien conseil de ne pas regarder status: "failed", retirez cette exception : ce statut est désormais fiable.

    2. Changement

      #Les en-têtes de débit ne sont plus émis deux fois

      Trois en-têtes x-ratelimit-* pouvaient sortir en double quand deux couches les posaient. Une seule les pose maintenant, et le filet de sécurité se retire quand une autre couche a déjà écrit la famille.

      Détail

      Ce que vous faites. Si vous coupiez ces valeurs sur la virgule, comme cette documentation le conseillait, ce n'est plus nécessaire. Une lecture directe en nombre entier fonctionne.

  13. #

    Trois changements sont partis ensemble. Aucun n'est une rupture de compatibilité.

    1. Changement

      #Le certificat public nomme la marque émettrice

      Le champ issuer_name porte le nom de la marque qui a émis le certificat. Le serveur le calcule à la lecture, et il vaut null quand le certificat n'est rattaché à aucune marque.

      Détail

      Trois surfaces sont concernées : GET /v1/certificate/{identifier}, le PDF rendu par GET /v1/certificate/{identifier}/download, et le bloc certificate de GET /v1/resolve/{identifier}.

      Ce que vous faites. Rien dans vos appels. Si vous avez recopié la valeur de issuer_name dans votre propre base, remplacez-la par ce que rend l'API aujourd'hui.

    2. Changement

      #Les refus d'authentification répondent en anglais

      La porte d'entrée de l'API rendait ses messages en deux langues selon le chemin appelé. Les messages qu'elle pose dans detail sont désormais en anglais : Not authenticated, Invalid token, Invalid token: missing email, Token has been revoked, Invalid JWT token, Account disabled, User not found et Partner account required (repairer or recycler).

      Détail

      Deux refus de POST /v1/originality/read-sig/verify suivent la même règle et valent maintenant Invalid uid_hex et Invalid signature_hex.

      Ce que vous faites. Rien, si vous branchez votre code sur le code HTTP, comme le recommande la page des erreurs. Un client qui compare ces phrases caractère par caractère doit reprendre ses comparaisons, ou mieux, les supprimer.

    3. Changement

      #Le portail partenaire a un plafond d'appels

      Les quatre points d'entrée /partner-portal acceptent 30 appels par fenêtre de 60 secondes, comptés par adresse d'appel. Les quatre partagent ce compteur, et la forme /v1/partner-portal/... le partage avec la forme sans préfixe.

      Détail

      Chaque réponse acceptée porte X-RateLimit-Limit, X-RateLimit-Remaining et X-RateLimit-Reset. Un dépassement renvoie 429, avec detail valant Rate limit exceeded: 30 requests per 60s et un en-tête Retry-After valant 60.

      Ce que vous faites. Espacez vos appels au portail et pilotez votre cadence sur X-RateLimit-Remaining. Un parcours humain, chercher un produit puis déclarer une intervention, reste très en dessous de ce plafond.

  14. #

    1. Changement

      #L'envoi d'un lot crée maintenant des produits

      POST /v1/partner/mint/batch répondait 200, son traitement allait jusqu'au bout, et aucun produit n'était créé. Il manquait à chaque ligne deux valeurs que le contrat d'envoi ne vous laisse pas fournir, et chaque ligne était rejetée pour cette raison.

      Détail

      Le serveur pose désormais ces deux valeurs lui-même, sur chaque ligne : la méthode d'identification qr et l'identifiant technique du produit.

      Ce que vous faites. Rien dans vos envois. Le contrat d'une ligne ne change pas, et une ligne qui porterait la méthode d'identification ou l'identifiant technique est refusée en 400, comme tout champ que le contrat ne nomme pas.

      Les produits créés par cette voie sont identifiés par QR. Pour des articles NFC, passez par la console.

  15. #

    Sept changements sont partis ensemble. Les quatre premiers sont des ruptures de compatibilité.

    1. Rupture de compatibilité

      #L'API refuse les champs owner_email et contract_address

      Une ligne de lot qui porte l'un de ces deux champs fait échouer toute la requête en 400. La réponse nomme le champ refusé.

      Détail

      L'API acceptait ces deux champs et ne les lisait jamais. La valeur que vous y mettiez n'avait aucun effet.

      Une ligne de lot accepte désormais cinq champs et rien d'autre : product_name, brand_id, category_id et metadata_uri, qui sont obligatoires, et external_ref, qui est facultatif.

      Ce que vous faites. Retirez ces deux colonnes de vos exports CSV et ces deux clefs de vos objets JSON. Si vous avez besoin de rattacher une ligne à une référence de votre côté, utilisez external_ref : c'est le seul champ libre du contrat. Nous le conservons sur la ligne du lot et nous le rendons dans le résultat que lit GET /v1/partner/mint/batch/status/{job_id}. Aucun champ de produit ne le reprend.

      Ce retrait fait passer la validation. Depuis le 21 août 2026, il fait aussi créer les produits, voir l'entrée de cette date.

    2. Rupture de compatibilité

      #Rejouer une clef d'idempotence avec un lot différent répond 409

      L'en-tête Idempotency-Key reste facultatif et reste mémorisé 24 heures.

      Détail

      Rejouer la même valeur avec le même lot rend toujours la réponse du premier appel, sans rien refrapper. L'ordre des contrôles a changé le même jour. Le rejeu consomme désormais un jeton de votre plafond de débit, et l'API relit votre corps de requête avant de vous rendre la réponse mise en cache. Un rejeu au-delà du plafond reçoit donc 429. Un rejeu dont le corps est devenu illisible reçoit 400.

      Rejouer la même valeur avec un lot différent répond maintenant 409. L'API ne met pas votre second lot en file, et la réponse vous le dit.

      JSON
      {
        "detail": "Idempotency-Key 'lot-2026-08-20-a' was already used with a different request body"
      }

      Le même code 409 signale aussi qu'un appel portant cette clef est encore en cours de traitement. Le message est alors A request with this Idempotency-Key is already being processed. Lisez le message avant de décider quoi faire : le premier cas vous demande de changer votre clef, le second vous demande d'attendre la fin du traitement en cours.

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

      Ce que vous faites. Fabriquez une valeur d'Idempotency-Key par lot distinct. Si votre code réutilise une valeur fixe, ou une valeur dérivée de l'heure à la minute, changez-la maintenant.

    3. Rupture de compatibilité

      #Le suivi de lot exige le droit mint:batch

      GET /v1/partner/mint/batch/status/{job_id} exige le droit mint:batch, le même que l'envoi du lot. Une clef qui ne le porte pas reçoit 403, et le message nomme le droit manquant.

      Détail

      Ce que vous faites. Vérifiez que la clef qui interroge le suivi porte mint:batch. Les droits d'une clef se fixent à sa création et aucun écran ne les modifie ensuite, donc il vous faut une nouvelle clef.

      Vérifiez d'abord votre plafond de clefs, qui ne compte que les clefs actives. L'offre Essentiel n'en autorise qu'une seule : révoquez l'ancienne, puis créez la nouvelle, en acceptant la coupure entre les deux. L'offre Prestige en autorise cinq : créez la nouvelle, déployez-la, puis révoquez l'ancienne. Révoquer une clef libère immédiatement une place. Une création au-delà du plafond répond 403 avec le code QUOTA_EXCEEDED.

    4. Rupture de compatibilité

      #Modifier un abonnement aux notifications exige que votre offre les comprenne

      PUT /v1/partner/webhooks/{webhook_id} regarde désormais l'offre de votre marque. Un appel qui passait la veille pour une marque dont l'offre ne comprend pas les notifications est refusé en 403.

      Détail
      JSON
      {
        "detail": {
          "code": "FEATURE_NOT_AVAILABLE",
          "feature": "webhooks"
        }
      }

      Une extinction seule fait exception : envoyer is_active à false et rien d'autre reste possible sans l'offre. Dès qu'un autre champ accompagne cette extinction, l'API exige l'offre.

      La création d'un abonnement, POST /v1/partner/webhooks, répond la même chose et l'exigeait déjà avant ce changement.

      Ce que vous faites. Rien si votre offre comprend les notifications. Si elle ne les comprend plus, arrêtez de modifier vos abonnements : éteignez-les avec is_active à false seul, ou supprimez-les.

    5. Changement

      #Un corps JSON illisible répond 400

      Un corps mal formé envoyé à POST /v1/partner/mint/batch répond maintenant 400, avec un message qui rappelle les deux formats acceptés.

      Détail
      JSON
      {
        "detail": "Corps de requete illisible : le JSON envoye est mal forme. Envoyez une liste d'objets produit en application/json, ou un fichier CSV en multipart/form-data."
      }

      Ce que vous faites. Si votre code rejoue automatiquement les réponses de la famille 5xx, ce cas ne le déclenche plus. Une requête refusée en 400 est à corriger, et la rejouer telle quelle donnera le même refus.

    6. Changement

      #Les huit points d'entrée à clef sont plafonnés en débit

      Le plafond d'appels s'applique maintenant aux huit points d'entrée de l'API partenaire, y compris le suivi de lot et les cinq points d'entrée d'abonnement aux notifications.

      Détail

      Deux conséquences visibles. Au-delà du plafond, vous recevez 429 avec l'en-tête Retry-After, exprimé en secondes. Quand le service de plafonnement est indisponible, vous recevez 503, et l'appel n'a aucun effet : une suppression refusée ainsi ne supprime rien.

      Les réponses qui passent portent X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset et X-RateLimit-Scope.

      Ce que vous faites. Lisez X-RateLimit-Remaining et ralentissez avant d'atteindre zéro. Si vous interrogez le suivi d'un lot en boucle, espacez vos appels : c'est le point d'entrée le plus exposé à ce plafond. Traitez le 503 comme une indisponibilité passagère et réessayez après un délai.

    7. Changement

      #Vos abonnements aux notifications restent lisibles et supprimables quelle que soit votre offre

      Lire la liste de vos abonnements, lire un abonnement et supprimer un abonnement fonctionnent quelle que soit l'offre de votre marque. Ces trois appels ne regardent pas votre offre.

      Détail

      L'envoi des notifications suit une autre règle : une marque dont l'offre ne comprend plus les notifications ne reçoit plus de livraison, quels que soient les abonnements encore enregistrés.

      Ce que vous faites. Rien. Si votre offre ne comprend plus les notifications, vous gardez la main pour inventorier vos abonnements et pour les supprimer.

  16. #

    1. Changement

      #Les adresses sans préfixe deviennent des alias permanents

      Les copies sans préfixe /v1 répondaient avec deux en-têtes qui annonçaient une fin de service au 1er septembre 2026, Deprecation et Sunset. L'API n'envoie plus ces deux en-têtes. Les adresses en /v1 ne les ont jamais portés.

      Détail

      Aucune date de fin de service n'est attachée à ces adresses. Si votre intégration les appelle déjà, elle continue de fonctionner.

      Ce que vous faites. Rien dans l'immédiat. Utilisez la forme /v1 pour toute nouvelle intégration : c'est la forme documentée partout sur ce site.

  17. #

    1. Changement

      #Un fichier gzip abîmé répond 400

      POST /v1/partner/mint/batch accepte un CSV compressé en gzip ou en zlib.

      Détail

      Un fichier gzip que le serveur ne parvient pas à décompresser répond maintenant 400, avec le motif dans le message. C'est une erreur de la requête, et votre code peut la traiter comme telle.

      Un fichier dont le contenu une fois décompressé dépasse le plafond répond 413, et le message indique ce plafond en Mio.

      Ce que vous faites. Rien si vos envois sont valides. Si vous compressez à la volée, vérifiez que le flux est complet avant de l'envoyer, et découpez vos lots pour rester sous le plafond. Un lot porte au plus 500 articles.

    2. Changement

      #Le suivi de lot rend quatre champs de plus quand la file a oublié votre lot

      GET /v1/partner/mint/batch/status/{job_id} répondait unknown au propriétaire d'un lot ancien, une fois la durée de rétention de la file écoulée. Il rend maintenant le statut réel, reconstruit à partir de la réservation enregistrée à l'envoi du lot, et il ajoute quatre champs : batch_status, items_count, success_count et error_count.

      Détail

      Un identifiant qui ne désigne aucun lot de votre marque répond toujours unknown, sans rien révéler de plus.

      Ce que vous faites. Rien, ces champs s'ajoutent.

  18. #

    1. Changement

      #Une intervention du portail partenaire porte un niveau de preuve

      POST /v1/partner-portal/interventions enregistre désormais un niveau de preuve avec chaque intervention, dans le champ proof_level.

      Détail

      Le corps de la requête accepte un champ facultatif proof_code. Sans lui, le niveau vaut declared. Avec un code valide, il vaut customer_code quand le code a été lu par le client, ou work_order quand il vient de l'ordre de travail de la marque. Un code refusé répond 422 et n'enregistre rien du tout : ni l'intervention, ni la consommation du code.

      Les interventions enregistrées avant cette date rendent proof_level vide. Une valeur vide ne vaut pas declared : la question n'avait pas été posée.

      Ce que vous faites. Si vous affichez l'historique des interventions, traitez la valeur vide comme un cas à part et n'affichez pas « déclaré » à sa place.

  19. #

    1. Rupture de compatibilité

      #Supprimer un abonnement demande de retaper son adresse

      DELETE /v1/partner/webhooks/{webhook_id} exige un paramètre d'URL confirm qui contient l'adresse exacte de l'abonnement.

      Détail

      Sans ce paramètre, un abonnement qui vous appartient répond 400 avec le code CONFIRMATION_REQUIRED. Avec une valeur qui ne correspond pas, la réponse est 400 avec le code CONFIRMATION_MISMATCH, et l'API ne modifie rien. Une suppression réussie répond 204.

      Un identifiant qui ne désigne aucun abonnement de votre marque répond 404, que vous ayez envoyé confirm ou non : l'API cherche votre abonnement avant de regarder votre confirmation.

      Un appel qui n'envoyait que l'identifiant est donc refusé.

      Ce que vous faites. Lisez l'adresse avec GET /v1/partner/webhooks/{webhook_id}, puis renvoyez-la dans confirm.

      curl
      curl -X DELETE \
        "https://api.sealtrust.io/v1/partner/webhooks/1?confirm=https%3A%2F%2Fexemple-sas.example%2Fwebhooks%2Fsealtrust" \
        -H "Authorization: Bearer st_live_0000000000000000000000000000000000000000000000"
    2. Changement

      #Chaque livraison porte le type d'événement

      Chaque livraison porte maintenant X-Webhook-Event, qui nomme le type d'événement. La signature ne couvre pas cet en-tête.

      Détail

      Ce que vous faites. Rien, cet en-tête s'ajoute.

  20. #

    1. Changement

      #Deux en-têtes s'ajoutent à chaque livraison

      Chaque livraison porte maintenant X-Webhook-Id et X-Webhook-Timestamp, en plus de X-Webhook-Signature.

      Détail

      X-Webhook-Id est l'empreinte du corps de la livraison. Les réessais d'une même livraison portent la même valeur, ce qui vous permet d'écarter un réessai. Deux événements distincts dont le corps est identique portent eux aussi la même valeur.

      Ce que vous faites. Dédoublonnez sur le couple formé du nom de l'événement, porté par X-Webhook-Event depuis le 11 août 2026, et de X-Webhook-Id. N'écartez jamais une livraison sur la valeur de X-Webhook-Timestamp : cet horodatage change à chaque réessai, donc il vous ferait traiter deux fois la même livraison.

    2. Changement

      #La signature porte les octets exacts qui vous sont envoyés

      X-Webhook-Signature a la forme t=<horodatage>,v1=<empreinte>. L'empreinte est un HMAC-SHA256 calculé avec le secret de votre abonnement, sur la chaîne formée de l'horodatage, d'un point, et du corps de la requête.

      Détail

      Le corps signé est la suite d'octets reçue sur le fil. Décoder le JSON puis le réencoder avant de vérifier casse la signature, parce que le réencodage ne rend pas les mêmes octets.

      Ce que vous faites. Vérifiez la signature sur le corps brut de la requête, avant tout décodage.

  21. #

    1. Changement

      #Les points d'entrée d'abonnement exigent des droits

      Lister vos abonnements et en lire un exigent le droit webhooks:read. Créer, modifier et supprimer un abonnement exigent le droit webhooks:write.

      Détail

      Une clef qui ne porte pas le droit reçoit 403, et le message nomme le droit manquant.

      Ce que vous faites. Vérifiez les droits de la clef qui gère vos abonnements. Si elle a été créée avec le seul droit mint:batch, créez une nouvelle clef portant aussi les droits de notification.

  22. #

    1. Changement

      #Le préfixe /v1 apparaît

      Chaque point d'entrée métier devient joignable à deux adresses, avec le préfixe /v1 et sans aucun préfixe.

      Détail

      Les adresses sans préfixe répondent en plus deux en-têtes, Deprecation: true et Sunset: 2026-09-01. Les adresses en /v1 ne les portent pas. L'entrée du 16 août 2026 retire ces deux en-têtes et aucune date de fin de service n'a été appliquée.

      Ce que vous faites. Rien. Les deux adresses appellent le même code.

#Ce que cette page ne fait pas

Elle n'annonce pas les changements à venir. Une entrée arrive ici quand le comportement décrit est en service.

Elle ne remplace pas un essai. Nous n'ouvrons aucun environnement de test aux partenaires, et aucune adresse de bac à sable n'existe. Après avoir lu une entrée marquée rupture, rejouez votre appel réel sur un produit ou un abonnement de votre marque que vous pouvez sacrifier, et comparez le code de réponse à celui que vous attendiez.

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