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
- Sur quoi porte un passeport
- Ce que contient un passeport
- Publier un passeport
- Lire le passeport
- Le bloc de provenance des informations
- Le passeport de référence d'un modèle
- Niveaux d'accès, qui voit quoi
- Définir vos propres règles d'accès
- Export JSON-LD
- Le passeport signé et sa vérification
- L'émetteur
- Récupérer l'attestation
- Vérifier l'attestation
- Voir le contenu avant signature
- Preuves d'intégrité
- Le contrôle d'intégrité
- Le résumé des preuves
- Vérifier la copie IPFS depuis la lecture
- Plafond d'appels et erreurs
- Ce qu'il faut retenir
- Pour aller plus loin
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 :
| Section | Contenu |
|---|---|
product_identity | GTIN, modèle, marque, pays de fabrication, site de production |
labels | étiquettes et mentions portées sur le produit |
compliance | conformité ESPR, REACH, marquage CE |
circularity | recyclabilité, contenu recyclé, indice de réparabilité, notice de démontage |
environmental_impact | empreinte carbone, eau, énergie, transport |
carbon_footprint | empreinte carbone détaillée |
energy_efficiency | classe énergétique, enregistrement EPREL |
durability | durée de vie attendue |
materials | composition matière |
substances_of_concern | substances préoccupantes et fiches de données de sécurité |
bill_of_materials | nomenclature des composants |
manufacturing | données de fabrication |
supply_chain | chaîne d'approvisionnement |
battery_specification | spé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"const response = await fetch(
"https://api.sealtrust.io/v1/passport/EXEMP1E00001",
);
const passeport = await response.json();
console.log(passeport.passport_version, passeport.access_tier);import requests
response = requests.get(
"https://api.sealtrust.io/v1/passport/EXEMP1E00001",
timeout=10,
)
response.raise_for_status()
passeport = response.json()
print(passeport["passport_version"], passeport["access_tier"])La réponse, au niveau public, a cette forme. Les valeurs sont inventées.
{
"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 "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.
{
"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.
| Niveau | Ce qu'il ajoute | Qui l'obtient |
|---|---|---|
public | identification, étiquettes, conformité ESPR / REACH / CE, recyclabilité et contenu recyclé, spécification générale de batterie | tout le monde, sans authentification |
end_user | impact environnemental, circularité complète, matière principale, durabilité, efficacité énergétique, empreinte carbone | tout le monde, sans authentification |
repairer | nomenclature, notice de démontage, indice de réparabilité, état de santé de batterie | compte authentifié accrédité réparateur sur la marque |
recycler | composition matière, substances préoccupantes, notice de démontage, état de santé de batterie | compte authentifié accrédité recycleur sur la marque |
upstream | composition matière, substances préoccupantes, fabrication, chaîne d'approvisionnement | compte authentifié de la marque, ou autorité |
authority | l'intégralité des données | compte 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,recycleretupstreamsont 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. Seulauthorityreç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.
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 -H "Authorization: Bearer JETON_DE_SESSION_FICTIF" \
"https://api.sealtrust.io/v1/passport/EXEMP1E00001?access_tier=repairer"Les refus sont explicites :
| Code | Condition |
|---|---|
| 401 | niveau professionnel ou autorité demandé sans authentification |
| 403 | authentifié, mais sans l'accréditation correspondante sur cette marque, et sans accès à la marque |
| 403 | niveau authority demandé par un compte qui ne porte pas ce rôle |
| 404 | produit 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"const response = await fetch(
"https://api.sealtrust.io/v1/passport/EXEMP1E00001?format=jsonld",
{ headers: { Accept: "application/ld+json" } },
);
const jsonld = await response.json();
console.log(jsonld["@context"], jsonld.name);import requests
response = requests.get(
"https://api.sealtrust.io/v1/passport/EXEMP1E00001",
params={"format": "jsonld"},
headers={"Accept": "application/ld+json"},
timeout=10,
)
response.raise_for_status()
jsonld = response.json()
print(jsonld["@context"], jsonld["name"]){
"@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 "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 "https://api.sealtrust.io/v1/passport/EXEMP1E00001/vc"{
"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"const response = await fetch(
"https://api.sealtrust.io/v1/passport/EXEMP1E00001/vc/verify",
);
const result = await response.json();
console.log(result.verified, result.issuer);import requests
response = requests.get(
"https://api.sealtrust.io/v1/passport/EXEMP1E00001/vc/verify",
timeout=10,
)
response.raise_for_status()
result = response.json()
print(result["verified"], result["issuer"]){
"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.
{
"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.
{
"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 -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.
| Code | Condition | Que faire |
|---|---|---|
| 401 | niveau professionnel ou autorité demandé sans session valide | authentifiez-vous |
| 403 | session valide, mais sans droit sur ce niveau pour cette marque | demandez le niveau qui correspond à votre accréditation |
| 404 | Product not found : aucun produit ne correspond à l'identifiant | vérifiez l'identifiant |
| 404 | No published passport found for this product : le produit existe, aucun passeport publié | publiez une version |
| 404 | Unknown GS1 Digital Link : aucun passeport de référence public ne répond pour ce GTIN | vérifiez le GTIN |
| 404 | aucune attestation émise pour ce passeport | republiez le passeport pour déclencher l'émission |
| 409 | modification du contenu d'une version scellée | publiez une nouvelle version |
| 429 | plafond 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
- Lire un passeport par identifiant : les paramètres, la réponse complète et les erreurs de la lecture décrite ici.
- Le résumé des preuves : le détail du sceau, de la copie IPFS et de l'ancrage pour un article donné.
- Confiance et preuves : la portée exacte de chaque preuve et ses limites.
- Conformité réglementaire : les obligations qui portent sur le contenu que vous publiez.
- Créer des produits : la création à l'unité et en lot, en amont de la publication d'un passeport.
Cette page vous a-t-elle été utile ?
Votre réponse ouvre un courriel pré-rempli dans votre messagerie, à destination de contact@sealtrust.io. Vous le relisez avant de l'envoyer.