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. #

    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.

  2. #

    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.

  3. #

    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.

  4. #

    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.

  5. #

    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.

  6. #

    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.

  7. #

    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.

  8. #

    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_test_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.

  9. #

    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.

  10. #

    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.

  11. #

    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