Publier un passeport numérique de produit

Publier un passeport, choisir qui voit quels champs, l'exporter en JSON-LD et le faire vérifier de façon indépendante.

Sur cette page

En quittant cette page, vous saurez ce qu'un passeport SealTrust contient, comment le publier depuis la console, comment son contenu est filtré selon la personne qui le lit, comment le récupérer en JSON ou en JSON-LD, et comment un tiers vérifie sa signature sans nous faire confiance.

Toutes les adresses citées ici sont publiques. La lecture d'un passeport au niveau public ne demande aucune clef d'API.

#Sur quoi porte un passeport

Un passeport s'attache soit à un modèle de produit, soit à un exemplaire précis.

Le passeport de référence est rattaché au modèle. Il est partagé par tous les articles du modèle. C'est le niveau qui convient à la plupart des produits, et il ne demande pas de sérialiser chaque exemplaire.

Un lot se couvre par le passeport de référence de son modèle. Le règlement ESPR autorise un passeport au niveau du modèle, du lot ou de l'article. SealTrust rattache un passeport à un modèle ou à une unité, donc une production par lots se couvre avec le passeport de référence du modèle : il vaut pour tous les exemplaires qui partagent le même code produit. Vous n'avez pas à créer un passeport par exemplaire pour produire par lots. Seules certaines batteries doivent porter un passeport à l'exemplaire, au titre du règlement (UE) 2023/1542.

Le passeport d'exemplaire est rattaché à une unité physique. Il porte les données propres à cette unité, par exemple un état de santé de batterie ou une déclaration liée à son numéro de série.

Quand vous demandez le passeport d'une unité, le serveur cherche d'abord un passeport rattaché à cette unité. S'il n'en trouve pas, il sert le passeport de référence de son modèle. Il ne sert jamais à sa place un passeport rattaché à une autre unité du même modèle.

#Ce que contient un passeport

Le contenu du passeport est un document JSON libre, organisé en sections. Les règles d'accès sont écrites contre les chemins de ces sections, ce qui fixe la liste des noms utilisés :

SectionContenu
product_identityGTIN, modèle, marque, pays de fabrication, site de production
labelsétiquettes et mentions portées sur le produit
complianceconformité ESPR, REACH, marquage CE
circularityrecyclabilité, contenu recyclé, indice de réparabilité, notice de démontage
environmental_impactempreinte carbone, eau, énergie, transport
carbon_footprintempreinte carbone détaillée
energy_efficiencyclasse énergétique, enregistrement EPREL
durabilitydurée de vie attendue
materialscomposition matière
substances_of_concernsubstances préoccupantes et fiches de données de sécurité
bill_of_materialsnomenclature des composants
manufacturingdonnées de fabrication
supply_chainchaîne d'approvisionnement
battery_specificationspécification générale d'une batterie
state_of_healthétat de santé d'une batterie

Les deux dernières sections viennent du règlement batteries (UE) 2023/1542. Leur présence dans un passeport suffit à le classer comme passeport de batterie pour le choix des règles d'accès.

Autour de ces données, la lecture par unité porte aussi des éléments que le serveur calcule lui-même : le nom du produit et de la marque, la photo du modèle, le GTIN, le lien GS1 Digital Link, un résumé de garantie, et un bloc de provenance des informations décrit plus bas. La lecture du passeport de référence d'un modèle en porte moins, la liste exacte figure plus bas.

#Publier un passeport

Dans la console, ouvrez Conformité DPP, puis l'onglet Passeports DPP. Vous y créez une version, vous la remplissez, et vous la publiez.

Quatre choses se produisent au moment où vous publiez.

Les autres versions publiées du même modèle cessent d'être publiées. Elles restent enregistrées, leur date de publication est effacée et leur visibilité passe à « marque seule ».

Le serveur scelle la version. Son contenu devient immuable. Le serveur refuse toute modification du contenu d'une version scellée, avec le code 409. Pour corriger une donnée, vous publiez une nouvelle version. Dépublier une version ne la descelle pas.

Le serveur dépose une copie publique sur IPFS. Il ne dépose que la projection publique du passeport. Les champs réservés aux niveaux professionnels n'y figurent pas. Si le dépôt échoue, la publication aboutit quand même et le passeport reste sans copie IPFS.

Le serveur émet ou rafraîchit l'attestation signée. Le passeport signé suit automatiquement la version publiée, sans étape supplémentaire. Si la signature échoue, la publication aboutit quand même et le passeport reste sans attestation.

Chaque version porte un numéro qui s'incrémente. Dès que le produit appartient à un modèle, le serveur compte ce numéro par modèle. Il ne le compte par unité que pour un produit qui n'appartient à aucun modèle.

Trois visibilités existent : public, owner_only et brand_only. Le serveur ne sert que public à un visiteur anonyme. Il sert en plus owner_only au propriétaire actuel de l'unité quand celui-ci est authentifié. Il ne sert jamais brand_only par la voie publique.

#Lire le passeport

Le point d'entrée est GET /v1/passport/{identifier}.

curl "https://api.sealtrust.io/v1/passport/EXEMP1E00001"

La réponse, au niveau public, a cette forme. Les valeurs sont inventées.

200 OK
{
  "id": 1,
  "product_id": 1,
  "brand_id": 42,
  "schema_version": "1.0",
  "passport_version": 3,
  "data": {
    "product_identity": {
      "gtin": "03701234567890",
      "model": "Cartable Exemple 32",
      "brand": "Exemple SAS",
      "made_in": "FR",
      "production_facility": "Atelier Exemple Nord"
    },
    "compliance": {
      "eu_espr": true,
      "reach": true
    },
    "circularity": {
      "recyclability_percentage": 62,
      "recycled_content_percentage": 0
    }
  },
  "data_hash": "0000000000000000000000000000000000000000000000000000000000000000",
  "ipfs_uri": null,
  "ipfs_gateway_url": null,
  "visibility": "public",
  "access_tier": "public",
  "is_owner": false,
  "published_at": "2026-08-01T09:00:00+00:00",
  "product_name": "Cartable Exemple 32",
  "brand_name": "Exemple SAS",
  "image_url": "https://exemple-sas.test/images/cartable-32.jpg",
  "gtin": "03701234567890",
  "gs1_digital_link": "https://id.gs1.org/01/03701234567890/21/EXEMP1E00001",
  "warranty": {
    "status": "active",
    "ends_at": "2028-08-01T09:00:00+00:00",
    "duration_months": 24,
    "transferable": true,
    "remaining_days": 730
  },
  "evidence": {
    "sections": {
      "identity": "verified",
      "integrity": "declared",
      "composition": "declared",
      "substances_of_concern": "declared"
    },
    "legend": {
      "declared": "Stated by the brand. Recorded, dated and attributable, but not independently checked.",
      "verified": "Checked mechanically against a public record; no declaration involved."
    },
    "derived": true,
    "note": "Statuses are computed by SealTrust from verifiable facts. A brand cannot set them."
  }
}

Le domaine du champ gs1_digital_link est celui du résolveur configuré pour votre intégration. https://id.gs1.org n'est que la valeur de repli, utilisée quand aucun résolveur n'est configuré. Ne codez pas ce domaine en dur, lisez la valeur renvoyée par la réponse.

Deux blocs n'apparaissent que quand ils ont lieu d'être. lifecycle apparaît quand l'unité a été remplacée ou retirée : le serveur continue de servir son passeport pour que l'identifiant résolve toujours. integrity apparaît quand vous ajoutez le paramètre verify_integrity=true et que vous appelez à un niveau professionnel ou autorité. Ce bloc est décrit plus bas.

#Le bloc de provenance des informations

Le bloc evidence dit sur quelle base chaque partie du passeport peut être crue. Trois valeurs existent :

  • verified : vérifié mécaniquement contre un registre public, sans déclaration.
  • document_backed : un document tiers est joint et peut être récupéré. Son contenu n'a pas été audité.
  • declared : déclaré par la marque. Enregistré, daté, attribuable, non vérifié de façon indépendante.

SealTrust calcule ces valeurs. Une marque ne peut pas les choisir. La section identity passe à verified quand l'unité existe sur la chaîne. La section integrity passe à verified quand l'empreinte de cette version a été ancrée et correspond toujours aux données enregistrées.

#Le passeport de référence d'un modèle

Pour lire le passeport que le GTIN annonce pour le modèle, utilisez GET /v1/passport/01/{gtin}.

curl
curl "https://api.sealtrust.io/v1/passport/01/03701234567890"

La réponse a cette forme, et elle n'en a pas d'autre. Les valeurs sont inventées.

200 OK
{
  "id": 7,
  "product_id": null,
  "product_model_id": 4,
  "gtin": "03701234567890",
  "level": "model",
  "brand_id": 42,
  "schema_version": "1.0",
  "passport_version": 2,
  "data": {
    "product_identity": {
      "gtin": "03701234567890",
      "model": "Cartable Exemple 32",
      "brand": "Exemple SAS",
      "made_in": "FR",
      "production_facility": "Atelier Exemple Nord"
    },
    "compliance": {
      "eu_espr": true,
      "reach": true
    },
    "circularity": {
      "recyclability_percentage": 62,
      "recycled_content_percentage": 0
    }
  },
  "data_hash": "0000000000000000000000000000000000000000000000000000000000000000",
  "ipfs_uri": "ipfs://bafyexemple0000000000000000000000000000000000000000000",
  "visibility": "public",
  "published_at": "2026-08-01T09:00:00+00:00",
  "product_name": "Cartable Exemple 32",
  "brand_name": "Exemple SAS"
}

Ce point d'entrée rend moins de champs que la lecture par unité. La réponse ne contient ni propriétaire, ni bannière de fin de vie, ni garantie : aucun de ces trois éléments n'existe au niveau du modèle. Elle ne contient pas non plus la photo du modèle, le lien GS1 Digital Link, le bloc de provenance des informations, le champ is_owner, ni le champ access_tier. Si votre intégration lit un de ces champs, lisez le passeport par unité.

Le serveur rend ici le champ ipfs_uri à tous les appelants, y compris anonymes, quel que soit le niveau demandé. Traitez le contenu de cette copie IPFS comme public. Ce point d'entrée ne rend jamais de champ ipfs_gateway_url.

Le paramètre access_tier fonctionne ici comme sur la lecture par unité : il filtre le contenu de data, et les niveaux professionnels et autorité exigent la même authentification. Le paramètre format=jsonld et le paramètre verify_integrity, eux, n'existent pas sur ce point d'entrée.

#Niveaux d'accès, qui voit quoi

Le paramètre access_tier décide des champs rendus. Il accepte six valeurs.

NiveauCe qu'il ajouteQui l'obtient
publicidentification, étiquettes, conformité ESPR / REACH / CE, recyclabilité et contenu recyclé, spécification générale de batterietout le monde, sans authentification
end_userimpact environnemental, circularité complète, matière principale, durabilité, efficacité énergétique, empreinte carbonetout le monde, sans authentification
repairernomenclature, notice de démontage, indice de réparabilité, état de santé de batteriecompte authentifié accrédité réparateur sur la marque
recyclercomposition matière, substances préoccupantes, notice de démontage, état de santé de batteriecompte authentifié accrédité recycleur sur la marque
upstreamcomposition matière, substances préoccupantes, fabrication, chaîne d'approvisionnementcompte authentifié de la marque, ou autorité
authorityl'intégralité des donnéescompte authentifié portant le rôle d'autorité de surveillance du marché

⚠️ Ces six valeurs ne se classent pas de la plus étroite à la plus large. repairer, recycler et upstream sont trois publics distincts : chacun ajoute au socle du consommateur ce que son propre métier exige, et aucun ne contient les champs d'un autre. Un recycleur accrédité ne lit donc pas la nomenclature réservée au réparateur, et détenir une accréditation n'en ouvre aucune autre. Seul authority reçoit l'intégralité.

Les trois niveaux professionnels et le niveau autorité exigent une authentification. Il s'agit d'une session de compte utilisateur, présentée soit par l'en-tête Authorization: Bearer <jeton de session>, soit par le cookie de session posé à la connexion. Une clef d'API partenaire n'ouvre pas ces niveaux.

Trois de ces niveaux correspondent à un métier : repairer, recycler et upstream. Ils ne se classent pas les uns au-dessus des autres. Un recycleur n'est pas au-dessus d'un réparateur. Chacun hérite du niveau public et du niveau utilisateur final, puis ajoute ce que son métier demande. Les accréditations délivrées à un partenaire sont de deux types, réparateur et recycleur, donc aucune accréditation n'ouvre upstream par elle-même.

Les profils d'accès au passeport, côte à côte

Un socle commun occupe le haut du schéma, servi sans authentification : le niveau public porte l'identification, les étiquettes, la conformité, la recyclabilité et la spécification générale de batterie, puis le niveau end_user y ajoute l'impact environnemental, la circularité, la durabilité et l'empreinte carbone. De ce socle partent trois flèches, vers trois cartes de même taille placées à la même hauteur. La première, repairer, revient à un compte accrédité réparateur sur la marque et ajoute la nomenclature, la notice de démontage, l'indice de réparabilité et l'état de santé de batterie. La deuxième, recycler, revient à un compte accrédité recycleur et ajoute la composition matière, les substances préoccupantes, la notice de démontage et l'état de santé de batterie. La troisième, upstream, revient à l'équipe de la marque ou à une autorité, et ajoute la composition matière, les substances préoccupantes, la fabrication et la chaîne d'approvisionnement. Entre chaque paire de cartes, un signe en forme de croix rappelle qu'aucune des trois ne reçoit ce que sa voisine ajoute. Une carte à part, en bas, porte le niveau authority de la surveillance du marché, qui reçoit l'intégralité des données quel que soit le métier.

Demandez le niveau de votre métier. C'est celui qui décrit ce que vous êtes, et il rend ce que votre métier demande.

curl
curl -H "Authorization: Bearer JETON_DE_SESSION_FICTIF" \
  "https://api.sealtrust.io/v1/passport/EXEMP1E00001?access_tier=repairer"

Les refus sont explicites :

CodeCondition
401niveau professionnel ou autorité demandé sans authentification
403authentifié, mais sans l'accréditation correspondante sur cette marque, et sans accès à la marque
403niveau authority demandé par un compte qui ne porte pas ce rôle
404produit introuvable, ou aucun passeport publié pour ce produit

Les réponses JSON portent l'en-tête X-DPP-Access-Tier avec le niveau effectivement servi. Lisez-le plutôt que de supposer. L'export JSON-LD, décrit plus bas, ne porte pas cet en-tête.

Deux comportements à connaître.

Le propriétaire actuel voit plus. Si l'appel porte une session valide et que le compte est le propriétaire actuel de l'unité, le serveur répond au niveau end_user à une demande au niveau public, et il sert en plus les passeports en visibilité owner_only. Le champ is_owner et l'en-tête X-DPP-Access-Tier signalent ce changement. Cette élévation ne vaut que pour la lecture JSON et JSON-LD, elle ne s'applique pas à l'attestation signée.

Aucune réponse ne se met en cache. Le serveur sert toute réponse avec Cache-Control: no-store, max-age=0, quel que soit le niveau demandé et quel que soit le format. Un cache partagé ne rend donc jamais un corps privilégié à l'appelant suivant. Pour réduire le nombre de vos appels, gardez le résultat dans votre propre cache applicatif, avec la durée de fraîcheur que votre usage tolère.

Le lien IPFS dépend du niveau, et du point d'entrée. Sur GET /v1/passport/{identifier}, les champs ipfs_uri et ipfs_gateway_url restent nuls aux niveaux public et end_user. Le serveur ne les renseigne qu'aux niveaux professionnels et autorité, qui sont authentifiés. Sur GET /v1/passport/01/{gtin}, le serveur rend ipfs_uri à tous les appelants, y compris anonymes.

#Définir vos propres règles d'accès

Par défaut, la répartition des champs entre niveaux est celle décrite ci-dessus. Vous pouvez la remplacer, marque par marque, dans Conformité DPP puis Règles d'accès. Une règle associe un chemin de champ à un niveau. Les chemins acceptent le caractère générique, par exemple materials.*.

Les règles peuvent être portées par groupe de produits. Les règles du groupe general s'appliquent partout ; les règles du groupe battery s'appliquent en plus aux passeports de batterie.

La suppression de toutes les règles d'une marque en une fois demande de retaper une phrase de confirmation. Cette phrase contient le nombre exact de règles qui seraient supprimées et la portée de l'opération. Elle vous est fournie par l'écran qui liste les règles. Sans elle, la suppression est refusée.

Cette confirmation existe parce que l'opération est un changement de confidentialité sur des données réglementées. Les champs que vous aviez restreints reviennent aux règles intégrées, ce qui peut les rendre lisibles publiquement.

#Export JSON-LD

Ajoutez format=jsonld pour obtenir le passeport sous forme de données liées, en vocabulaire Schema.org et GS1. Le serveur rend cette réponse avec le type de contenu application/ld+json.

curl -H "Accept: application/ld+json" \
  "https://api.sealtrust.io/v1/passport/EXEMP1E00001?format=jsonld"
200 OK
{
  "@context": {
    "@vocab": "https://schema.org/",
    "gs1": "https://gs1.org/voc/",
    "espr": "https://data.europa.eu/espr/"
  },
  "@type": "Product",
  "identifier": "0x0000000000000000000000000000000000000000000000000000000000000000",
  "gtin": "03701234567890",
  "name": "Cartable Exemple 32",
  "brand": {
    "@type": "Brand",
    "name": "Exemple SAS",
    "url": "https://exemple-sas.test"
  },
  "countryOfOrigin": "FR",
  "gs1:productionFacility": "Atelier Exemple Nord",
  "material": [],
  "additionalProperty": [
    {
      "@type": "PropertyValue",
      "name": "Recyclability (EN 45555)",
      "value": 62,
      "unitText": "percent"
    },
    {
      "@type": "PropertyValue",
      "name": "gs1:recycledContentPercentage",
      "value": 0,
      "unitText": "percent"
    }
  ],
  "espr:compliance": {
    "@type": "espr:ComplianceDeclaration",
    "espr:euEsprCompliant": true,
    "espr:reachCompliant": true
  },
  "warranty": {
    "@type": "WarrantyPromise",
    "durationOfWarranty": {
      "@type": "QuantitativeValue",
      "value": 24,
      "unitCode": "MON"
    },
    "espr:warrantyStatus": "active",
    "espr:warrantyEndDate": "2028-08-01T09:00:00+00:00",
    "espr:transferable": true
  }
}

Quatre points à retenir.

L'export JSON-LD est construit après le filtrage par niveau. Un appel anonyme obtient donc la projection publique en JSON-LD, et rien de plus. Pour obtenir les champs professionnels en JSON-LD, ajoutez access_tier et authentifiez-vous.

Les noms de clés changent entre les deux formats. La section compliance du JSON devient un nœud espr:compliance de type espr:ComplianceDeclaration, dont les clés sont espr:euEsprCompliant, espr:reachCompliant, espr:ceMarking et leurs voisines. N'attendez pas les noms de la réponse JSON dans le JSON-LD.

Le serveur retire du document les clés sans valeur. Un champ que vous n'avez pas rempli n'apparaît pas sous forme de null.

Cette réponse ne porte pas l'en-tête X-DPP-Access-Tier, contrairement à la réponse JSON. Le niveau servi est celui que vous avez demandé dans access_tier.

Le paramètre format n'existe que sur GET /v1/passport/{identifier}. Le point d'entrée du passeport de référence d'un GTIN ne le propose pas.

#Le passeport signé et sa vérification

Le serveur émet aussi chaque passeport publié sous forme d'attestation vérifiable, au format SD-JWT-VC. Cette attestation permet à un tiers de vérifier que la marque a bien émis ce passeport, sans passer par notre point d'entrée de vérification et sans nous accorder de confiance.

#L'émetteur

L'émetteur est un identifiant décentralisé did:web porté par la marque. Sa forme par défaut est did:web:api.sealtrust.io:brand:{brand_id}, qui se résout, selon la spécification did:web, à l'adresse https://api.sealtrust.io/brand/{brand_id}/did.json.

curl
curl "https://api.sealtrust.io/brand/4242/did.json"

Le document rendu liste toutes les clefs publiques non révoquées de la marque, chacune sous forme de JsonWebKey2020. L'identifiant d'une clef est <did>#key-<version>, ce qui permet à une attestation ancienne de rester vérifiable après une rotation de clef, tant que l'ancienne version n'est pas révoquée.

Une marque peut aussi porter son identifiant sur son propre domaine. Le document se trouve alors à https://<domaine de la marque>/.well-known/did.json, et l'identifiant prend la forme did:web:<domaine de la marque>. La marque conserve ainsi la propriété de son identité d'émetteur.

#Récupérer l'attestation

GET /v1/passport/{identifier}/vc rend la présentation filtrée selon le niveau demandé. Cette voie applique la même grille de niveaux que la lecture JSON, avec deux différences :

  • l'élévation automatique du propriétaire ne s'applique pas. Un propriétaire authentifié reste au niveau qu'il demande, et le serveur ne sert pas les passeports en visibilité owner_only ;
  • une unité remplacée ou retirée renvoie 404, alors que la lecture JSON continue de servir son passeport avec la bannière lifecycle.

Ces deux différences valent pour les trois adresses de l'attestation : /vc, /vc/verify et /vc/preview.

curl
curl "https://api.sealtrust.io/v1/passport/EXEMP1E00001/vc"
200 OK
{
  "passport_id": 1,
  "issuer": "did:web:api.sealtrust.io:brand:4242",
  "vct": "https://schema.sealtrust.io/vct/digital-product-passport",
  "access_tier": "public",
  "format": "dc+sd-jwt",
  "sd_jwt_vc": "eyJFWEVNUExFIn0.eyJFWEVNUExFIn0.RVhFTVBMRQ~WyJFWEVNUExFIl0~"
}

La divulgation est sélective. Le serveur laisse en clair les champs du niveau public et remplace les autres par des empreintes. Il ne révèle une empreinte que si le niveau demandé y donne droit.

Deux blocs échappent à cette règle. Le serveur les porte toujours en clair, quel que soit le niveau demandé, et il ne les masque jamais :

  • l'identité de la marque : raison sociale, identifiant LEI, numéro EORI, site, adresse postale et courriel de contact ;
  • l'identité de l'unité : empreinte d'UID, identifiant de jeton et nom du produit.

Une présentation au niveau public révèle donc la projection publique, plus ces deux blocs. La lecture JSON au niveau public, elle, ne rend de la marque que son nom. Vérifiez le contenu de la fiche de votre marque avant de diffuser des attestations : son adresse postale et son courriel de contact partent avec.

Si aucune attestation n'a encore été émise pour ce passeport, la réponse est 404.

#Vérifier l'attestation

Deux chemins existent, et le second ne dépend pas de nous.

Par notre point d'entrée. GET /v1/passport/{identifier}/vc/verify vérifie la présentation correspondant au niveau demandé contre la clef publique de la marque.

curl "https://api.sealtrust.io/v1/passport/EXEMP1E00001/vc/verify"
200 OK
{
  "passport_id": 1,
  "issuer": "did:web:api.sealtrust.io:brand:4242",
  "vct": "https://schema.sealtrust.io/vct/digital-product-passport",
  "key_version": 1,
  "access_tier": "public",
  "verified": true,
  "error": null,
  "credential_subject": {
    "product_identity": {
      "gtin": "03701234567890",
      "model": "Cartable Exemple 32",
      "brand": "Exemple SAS",
      "made_in": "FR",
      "production_facility": "Atelier Exemple Nord"
    }
  }
}

En cas d'échec, verified vaut false, error vaut verification_failed et credential_subject est nul. Le message d'erreur d'origine n'est jamais rendu.

Par vos propres moyens. Récupérez la présentation avec GET /v1/passport/{identifier}/vc, récupérez le document de l'émetteur à l'adresse did.json indiquée plus haut, choisissez la clef dont l'identifiant correspond au champ kid de l'en-tête de l'attestation, et vérifiez la signature. L'algorithme de signature est ES256, et le type déclaré dans l'en-tête est dc+sd-jwt. N'importe quelle bibliothèque did:web et SD-JWT-VC standard suffit.

#Voir le contenu avant signature

GET /v1/passport/{identifier}/vc/preview rend l'enveloppe de l'attestation et son contenu pour un niveau donné, sans signature. La réponse porte "signed": false.

L'aperçu rend le contenu du niveau demandé au format JSON-LD. L'attestation signée, elle, porte l'arbre de données brut du passeport, avec les noms de sections de la réponse JSON. Servez-vous de l'aperçu pour vérifier quels champs un niveau donné voit. Sa structure n'est pas celle de l'attestation, donc ne l'utilisez pas comme gabarit d'intégration.

#Preuves d'intégrité

Deux points d'entrée publics accompagnent le passeport. Ils sont sans authentification.

#Le contrôle d'intégrité

GET /v1/passport/{identifier}/verify recalcule l'empreinte des données, la compare à celle enregistrée, compare la copie IPFS quand elle existe, et recalcule l'empreinte de version d'une version scellée.

200 OK
{
  "db_hash_match": true,
  "ipfs_match": true,
  "ipfs_uri": "ipfs://bafyexemple0000000000000000000000000000000000000000000",
  "ipfs_gateway_url": "https://ipfs.io/ipfs/bafyexemple0000000000000000000000000000000000000000000",
  "data_hash": "0000000000000000000000000000000000000000000000000000000000000000",
  "computed_hash": "0000000000000000000000000000000000000000000000000000000000000000",
  "passport_version": 3,
  "seal": {
    "sealed": true,
    "sealed_at": "2026-08-01T09:00:00+00:00",
    "algorithm": "st-dpp-chain-v1",
    "version_hash": "0000000000000000000000000000000000000000000000000000000000000000",
    "prev_version_hash": "0000000000000000000000000000000000000000000000000000000000000000",
    "linked": true,
    "chain_link_match": true
  }
}

Soyez précis sur ce que chaque ligne établit.

db_hash_match compare une donnée que nous détenons à une empreinte que nous détenons. C'est un contrôle de cohérence interne.

ipfs_match compare la copie publique déposée sur IPFS à la projection publique attendue. La copie est adressée par son empreinte, donc elle ne peut pas être modifiée sans changer d'adresse. Quand la copie ne peut pas être récupérée, la valeur est null, ce qui veut dire « inconnu ». Elle ne bascule jamais à false pour une simple panne de passerelle.

version_hash porte l'empreinte de cette version, 64 caractères hexadécimaux. prev_version_hash porte l'empreinte de la version précédente, et il vaut null pour la première version scellée. Chaque empreinte de version couvre celle qui la précède, donc réécrire une version détache toutes celles publiées après elle.

chain_link_match est le signal de falsification. Si chain_link_match vaut false, la version a été modifiée après sa publication. Un passeport publié avant l'existence de ce chaînage porte linked: false et reason: sealed_before_chain, et aucune empreinte de version n'est fabriquée après coup pour une publication que nous ne pouvons pas dater.

Le serveur ne rend le lien IPFS ici que si la copie déposée est prouvée identique à la projection publique. Dans le cas contraire, il retire le lien de la réponse.

#Le résumé des preuves

GET /v1/passport/{identifier}/proof rassemble tout ce qu'un tiers peut vérifier. Le même résumé existe pour un passeport de référence à GET /v1/passport/01/{gtin}/proof.

200 OK
{
  "passport_version": 3,
  "data_hash": "0000000000000000000000000000000000000000000000000000000000000000",
  "ipfs_uri": "ipfs://bafyexemple0000000000000000000000000000000000000000000",
  "ipfs_gateway_url": "https://ipfs.io/ipfs/bafyexemple0000000000000000000000000000000000000000000",
  "anchor": {
    "chain": "base",
    "chain_id": 8453,
    "type": "merkle_batch",
    "tx_hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
    "basescan_url": "https://basescan.org/tx/0x0000000000000000000000000000000000000000000000000000000000000000",
    "merkle_root": "0x0000000000000000000000000000000000000000000000000000000000000000",
    "anchored": true,
    "proves": "batch_inclusion"
  },
  "passport_anchor": {
    "chain": "base",
    "chain_id": 8453,
    "tx_hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
    "basescan_url": "https://basescan.org/tx/0x0000000000000000000000000000000000000000000000000000000000000000",
    "merkle_root": "0x0000000000000000000000000000000000000000000000000000000000000000",
    "leaf": "0x0000000000000000000000000000000000000000000000000000000000000000",
    "leaf_index": 0,
    "proof": [],
    "anchored_at": "2026-08-01T10:00:00+00:00",
    "data_hash_matches": true,
    "proves": "content_existed_at_or_before_tx"
  },
  "seal": {
    "sealed": true,
    "sealed_at": "2026-08-01T09:00:00+00:00",
    "algorithm": "st-dpp-chain-v1",
    "version_hash": "0000000000000000000000000000000000000000000000000000000000000000",
    "prev_version_hash": "0000000000000000000000000000000000000000000000000000000000000000",
    "linked": true,
    "chain_link_match": true
  },
  "vc": {
    "issued": true,
    "vct": "https://schema.sealtrust.io/vct/digital-product-passport",
    "issued_at": "2026-08-01T09:00:00+00:00"
  },
  "verifications": {
    "count": 12,
    "last_verified_at": "2026-08-12T14:32:00+00:00"
  }
}

Deux blocs portent une référence sur la chaîne, et ils ne disent pas la même chose.

anchor date l'article. Lisez le champ anchored. Le nom de la clef ne suffit pas. Quand anchored vaut true, type vaut merkle_batch et proves vaut batch_inclusion : la racine du lot a été inscrite sur la chaîne et l'appartenance de l'unité à ce lot est démontrable. Quand anchored vaut false, type vaut mint_transaction et proves vaut token_minted : la transaction prouve seulement que le jeton existe. Elle ne dit rien du lot, et rien du contenu du passeport.

passport_anchor date le document. Il porte l'empreinte de cette version précise, sa preuve d'appartenance à un arbre, et la transaction qui a inscrit la racine. Il établit une seule propriété, content_existed_at_or_before_tx : ce contenu existait au plus tard à cette transaction. Il ne rend pas le contenu vrai, et il n'empêche pas de publier une correction en version suivante. Le champ data_hash_matches à false signifie que les données enregistrées ne correspondent plus à ce qui a été ancré.

L'inscription se fait sur Base, chain_id 8453.

Entre la publication et un éventuel ancrage, c'est le sceau qui tient. Il ne demande rien à la chaîne : il chaîne chaque version à la précédente et se recalcule à la lecture.

Pour un passeport de référence, deux blocs sont absents et cette absence est la réponse juste : anchor date un article, or une référence n'en a pas, et verifications compte des vérifications d'UID, or une référence n'a pas d'UID. La réponse porte en plus "level": "model" et le GTIN.

#Vérifier la copie IPFS depuis la lecture

Ajoutez verify_integrity=true à la lecture du passeport pour obtenir en plus un bloc integrity. Le serveur ne calcule ce bloc que s'il vous rend le lien IPFS, donc seulement aux niveaux professionnels et autorité. L'appel doit être authentifié et porter un access_tier :

curl
curl -H "Authorization: Bearer JETON_DE_SESSION_FICTIF" \
  "https://api.sealtrust.io/v1/passport/EXEMP1E00001?access_tier=repairer&verify_integrity=true"

Un appel anonyme au niveau public ou end_user ne reçoit jamais ce bloc, même avec verify_integrity=true. Pour un contrôle d'intégrité sans authentification, utilisez GET /v1/passport/{identifier}/verify décrit juste au-dessus.

#Plafond d'appels et erreurs

Toutes les adresses commençant par /passport partagent un plafond de 60 appels par tranche de 60 secondes et par adresse IP. Les formes /passport/... et /v1/passport/... comptent sur le même compteur. Un dépassement renvoie 429, avec l'en-tête Retry-After en secondes.

CodeConditionQue faire
401niveau professionnel ou autorité demandé sans session valideauthentifiez-vous
403session valide, mais sans droit sur ce niveau pour cette marquedemandez le niveau qui correspond à votre accréditation
404Product not found : aucun produit ne correspond à l'identifiantvérifiez l'identifiant
404No published passport found for this product : le produit existe, aucun passeport publiépubliez une version
404Unknown GS1 Digital Link : aucun passeport de référence public ne répond pour ce GTINvérifiez le GTIN
404aucune attestation émise pour ce passeportrepubliez le passeport pour déclencher l'émission
409modification du contenu d'une version scelléepubliez une nouvelle version
429plafond d'appels dépasséralentissez

#Ce qu'il faut retenir

Le passeport porte sur un modèle ou sur un exemplaire. Un lot se couvre par le passeport de référence de son modèle. Une version publiée est scellée et ne se modifie plus ; une correction devient la version suivante, et publier dépublie tout le modèle.

Le contenu rendu dépend du niveau demandé, et les niveaux professionnels exigent une session authentifiée accompagnée de l'accréditation correspondante sur la marque. Les trois métiers sont côte à côte : chacun part du socle commun, et aucun ne reçoit ce qu'un autre ajoute.

Le JSON-LD porte le même contenu, filtré de la même façon, avec d'autres noms de clés et sans les en-têtes de la réponse JSON.

L'attestation signée permet une vérification indépendante. Le sceau, la copie IPFS et l'ancrage disent chacun une chose précise, et la réponse nomme cette chose plutôt que de la laisser deviner.

#Pour aller plus loin

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