# GET /passport/01/{gtin}/proof

Récupérer les preuves publiques du passeport de référence annoncé par un GTIN : empreinte du contenu, copie IPFS vérifiée, ancrage du document sur Base et état de l'attestation signée. Point d'entrée public.

Source : https://docs.sealtrust.io/reference/get-passport-gtin-proof/

---

Vous récupérez les preuves publiques du passeport que ce GTIN annonce pour un
modèle. En quittant cette page, vous saurez demander ces preuves à partir d'un
GTIN seul, lire ce que chaque bloc établit, et interpréter correctement
l'absence d'un bloc.

Adresse complète :

```http
GET https://api.sealtrust.io/v1/passport/01/{gtin}/proof
```

Le même point d'entrée répond aussi sans le préfixe `/v1`, à
`https://api.sealtrust.io/passport/01/{gtin}/proof`. Les deux adresses appellent
le même code. Utilisez la forme `/v1` pour une nouvelle intégration.

> [!INFO] Ce point d'entrée porte sur un modèle
> Un GTIN identifie une référence commerciale. Il ne désigne aucun exemplaire
> physique. Ce point d'entrée sert donc les preuves du passeport de niveau
> référence, celui qui est rattaché à un modèle et à aucun exemplaire. Deux
> blocs présents sur la version par exemplaire sont absents ici, et leur absence
> est la réponse juste : `anchor`, qui date un exemplaire sur la chaîne, et
> `verifications`, qui compte les vérifications d'une étiquette physique. Un
> modèle n'a ni exemplaire ni étiquette.

## Autorisation

Aucune, point d'entrée public. Vous n'envoyez ni clef d'API, ni session, ni
en-tête d'origine. La réponse est la même pour tout le monde.

## Plafond d'appels

60 appels par tranche de 60 secondes, comptés par adresse réseau appelante.

Ce compteur est commun à tous les chemins qui commencent par `/passport`. Les
appels que vous adressez à l'un d'eux entament donc le budget des autres. Le
préfixe `/v1` ne crée pas un second budget : `/v1/passport/01/03701234567890/proof`
et `/passport/01/03701234567890/proof` remplissent le même compteur.

Chaque réponse acceptée porte trois en-têtes.

| En-tête | Contenu |
| --- | --- |
| `X-RateLimit-Limit` | le plafond appliqué sur la fenêtre, ici `60` |
| `X-RateLimit-Remaining` | ce qu'il vous reste dans la fenêtre en cours |
| `X-RateLimit-Reset` | l'horodatage de fin de la fenêtre, en secondes |

Un refus renvoie 429, avec ces trois en-têtes et `Retry-After`. Sur ce point
d'entrée, `Retry-After` vaut la durée de la fenêtre, soit 60 secondes.

## Paramètres de chemin et de requête

| Nom | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `gtin` | `string` | oui | Le numéro d'article commercial, l'identifiant GS1 que porte le code produit. |

Ce point d'entrée n'a aucun paramètre de requête.

Nous ramenons le GTIN à sa forme canonique de 14 chiffres avant la recherche.
Nous retirons tout caractère qui n'est pas un chiffre, puis nous complétons par
des zéros à gauche. Un GTIN à 8, 12 ou 13 chiffres retrouve donc le même modèle
qu'un GTIN à 14 chiffres. Nous refusons en 404 une valeur vide, une valeur sans
aucun chiffre, ou une valeur de plus de 14 chiffres.

Un GTIN se termine par une clef de contrôle, le chiffre calculé à partir de
ceux qui le précèdent. Nous la vérifions, et nous refusons en 400 un GTIN dont
le dernier chiffre ne correspond pas. Recopiez le code tel qu'il est imprimé
sur le produit, chiffre pour chiffre.

Le champ `gtin` de la réponse vous rend la forme à 14 chiffres retenue.

### En-têtes

Aucun en-tête n'est requis.

## Corps de la requête

Aucun. Cette requête n'a pas de corps.

## Requête d'exemple

Preuves du passeport de référence annoncé par le GTIN `03701234567890`.

> [!INFO] Le SDK TypeScript ne couvre pas ce point d'entrée
> Aucune méthode du paquet `@sealtrust-io/sdk` n'appelle cette adresse.
> L'exemple TypeScript ci-dessous utilise `fetch`, sans dépendance.

:::onglets
```bash title="curl"
curl -i https://api.sealtrust.io/v1/passport/01/03701234567890/proof
```
```typescript
const reponse = await fetch(
  "https://api.sealtrust.io/v1/passport/01/03701234567890/proof",
);

if (reponse.status === 404) {
  console.log("Aucun passeport de référence publié pour ce GTIN.");
} else if (reponse.ok) {
  const preuves = await reponse.json();
  console.log(preuves.gtin, preuves.level, preuves.passport_version);
  console.log(preuves.data_hash);
  console.log(preuves.ipfs_gateway_url);
  console.log(preuves.passport_anchor);
  console.log(preuves.seal);
  console.log(preuves.vc);
} else {
  console.log(reponse.status, await reponse.json());
}
```
```python
import requests

response = requests.get(
    "https://api.sealtrust.io/v1/passport/01/03701234567890/proof",
    timeout=30,
)

if response.status_code == 404:
    print("Aucun passeport de référence publié pour ce GTIN.")
elif response.ok:
    preuves = response.json()
    print(preuves["gtin"], preuves["level"], preuves["passport_version"])
    print(preuves.get("data_hash"))
    print(preuves.get("ipfs_gateway_url"))
    print(preuves.get("passport_anchor"))
    print(preuves["seal"])
    print(preuves["vc"])
else:
    print(response.status_code, response.json())
```
:::

## Réponse d'exemple

Code HTTP `200`.

```json
{
  "passport_version": 3,
  "data_hash": "1111111111111111111111111111111111111111111111111111111111111111",
  "ipfs_uri": "ipfs://bafybeiaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
  "ipfs_gateway_url": "https://ipfs.io/ipfs/bafybeiaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
  "passport_anchor": {
    "chain": "base",
    "chain_id": 8453,
    "tx_hash": "0x2222222222222222222222222222222222222222222222222222222222222222",
    "basescan_url": "https://basescan.org/tx/0x2222222222222222222222222222222222222222222222222222222222222222",
    "merkle_root": "0x3333333333333333333333333333333333333333333333333333333333333333",
    "leaf": "0x4444444444444444444444444444444444444444444444444444444444444444",
    "leaf_index": 7,
    "proof": [
      "0x5555555555555555555555555555555555555555555555555555555555555555",
      "0x6666666666666666666666666666666666666666666666666666666666666666"
    ],
    "anchored_at": "2026-08-14T09:12:44.318000+00:00",
    "data_hash_matches": true,
    "proves": "content_existed_at_or_before_tx"
  },
  "seal": {
    "sealed": true,
    "sealed_at": "2026-08-12T10:04:11.882000+00:00",
    "algorithm": "st-dpp-chain-v1",
    "version_hash": "7777777777777777777777777777777777777777777777777777777777777777",
    "prev_version_hash": "8888888888888888888888888888888888888888888888888888888888888888",
    "linked": true,
    "chain_link_match": true
  },
  "vc": {
    "issued": true,
    "vct": "https://schema.sealtrust.io/vct/digital-product-passport",
    "issued_at": "2026-08-12T10:04:12.140000+00:00"
  },
  "level": "model",
  "gtin": "03701234567890"
}
```

La réponse porte aussi l'en-tête `Cache-Control: no-store, max-age=0`. Ne
mettez cette réponse dans aucun cache partagé. 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.

Cinq champs sont toujours présents : `passport_version`, `seal`, `vc`, et,
propres à ce point d'entrée, `level` et `gtin`. Les autres champs
n'apparaissent que lorsque la preuve correspondante est établie. Une absence
recouvre deux situations que la réponse ne distingue pas : la preuve n'existe
pas encore, ou nous n'avons pas pu l'établir au moment de votre appel. Ne lisez
jamais une absence comme une altération.

| Champ | Type | Description |
| --- | --- | --- |
| `passport_version` | `integer` | Le numéro de la version de passeport servie. Une correction publiée crée une nouvelle version. Quand plusieurs versions de référence sont publiées et visibles publiquement pour ce modèle, nous servons celle qui porte le numéro de version le plus élevé. |
| `data_hash` | `string` | L'empreinte SHA-256 du contenu de cette version, 64 caractères hexadécimaux sans préfixe `0x`. Absent quand la version n'en porte pas. |
| `ipfs_uri` | `string` | L'adresse IPFS de la copie publique figée de ce passeport. Voir ci-dessous la condition qui commande sa présence. |
| `ipfs_gateway_url` | `string` | La même copie, servie par une passerelle HTTP publique, pour l'ouvrir dans un navigateur. Présent chaque fois que `ipfs_uri` est présent. Les deux champs vont ensemble. |
| `passport_anchor` | `object` | L'ancrage qui date le contenu de cette version sur la chaîne Base. Absent tant que la version n'a pas été inscrite sur la chaîne, et aussi lorsque nous ne pouvons pas reconstruire la preuve d'inclusion au moment de votre appel. Voir ci-dessous. |
| `seal` | `object` | Le sceau de la version et sa place dans la suite des versions. Toujours présent. Voir ci-dessous. |
| `vc` | `object` | L'état de l'attestation signée du passeport. Toujours présent. Voir ci-dessous. |
| `level` | `string` | Toujours `model`. Rappelle que ces preuves portent sur un modèle. |
| `gtin` | `string` | Le GTIN à 14 chiffres retenu après normalisation de la valeur que vous avez envoyée. |

### Le bloc `passport_anchor`

Ce bloc établit une seule chose : le contenu de cette version existait au plus
tard au moment de la transaction. C'est ce que dit son champ `proves`, dont la
valeur est `content_existed_at_or_before_tx`. Il ne rend pas le contenu vrai, et
il n'empêche pas la marque de publier une correction plus tard.

| Champ | Type | Description |
| --- | --- | --- |
| `chain` | `string` | Le réseau, `base` en production. |
| `chain_id` | `integer` ou `null` | L'identifiant de chaîne, `8453` pour Base en production. `null` sur les inscriptions les plus anciennes, où nous n'avions pas enregistré la chaîne ; lisez alors `chain`. |
| `tx_hash` | `string` | La transaction qui porte l'inscription de la racine. |
| `basescan_url` | `string` | Le lien direct vers cette transaction sur l'explorateur public du réseau. |
| `merkle_root` | `string` | La racine inscrite sur la chaîne, `0x` suivi de 64 caractères hexadécimaux. |
| `leaf` | `string` | L'empreinte de cette version dans l'arbre, `0x` suivi de 64 caractères hexadécimaux. |
| `leaf_index` | `integer` | La position de cette empreinte dans la liste des empreintes inscrites ensemble. Le comptage démarre à 0. |
| `proof` | `string[]` | Les empreintes voisines à combiner avec `leaf` pour retrouver `merkle_root`. La liste est vide quand l'inscription ne couvrait qu'une version. |
| `anchored_at` | `string` ou `null` | L'instant de l'inscription, au format ISO 8601. `null` quand cet instant n'a pas été enregistré. |
| `data_hash_matches` | `boolean` | `true` quand l'empreinte inscrite alors est encore l'empreinte du contenu servi aujourd'hui. `false` est le signal d'altération, et il est publié. |
| `proves` | `string` | Toujours `content_existed_at_or_before_tx`. |

Vous pouvez recalculer la racine vous-même. Vous partez de `leaf`. Pour chaque
élément de `proof`, dans l'ordre, vous mettez les deux valeurs de 32 octets côte
à côte, la plus petite des deux d'abord, puis vous appliquez keccak256 à la
concaténation. Le résultat devient la nouvelle valeur de travail. Après le
dernier élément de `proof`, vous devez obtenir exactement `merkle_root`. C'est
la convention de vérification d'OpenZeppelin. Nous trions les voisins à chaque
étage, donc la preuve n'a pas besoin d'indiquer un sens.

Il vous reste à vérifier que `merkle_root` est bien la valeur inscrite sur la
chaîne. Ouvrez `basescan_url` pour lire la transaction d'inscription.

> [!ATTENTION] Le lien entre `leaf` et `data_hash` n'est pas recalculable depuis cette seule réponse
> Le calcul de `leaf` fait intervenir, en plus de `passport_version` et de
> `data_hash`, un identifiant interne du passeport qui ne figure pas dans cette
> réponse. Le champ `data_hash_matches` est le résultat de cette comparaison,
> faite par nos soins. La chaîne qui va de `leaf` à `merkle_root`, elle, reste
> vérifiable par vos propres moyens.

Un `passport_anchor` absent ne veut pas dire que le passeport n'est pas
fiable. L'inscription sur la chaîne est une opération que SealTrust déclenche.
Une marque ne la commande pas depuis sa console, et beaucoup de versions
publiées ne sont jamais inscrites. Publier une version reste une écriture en
base de données, sans transaction sur la chaîne. Le bloc `seal`, lui, est
présent sur toute version scellée, et c'est lui qui rend visible une réécriture
tant qu'aucune inscription ne couvre la version.

### Le bloc `seal`

Nous fixons le sceau à la première publication d'une version. Il chaîne cette
version à la précédente du même passeport, ce qui rend visible une réécriture
survenue après coup.

| Champ | Type | Description |
| --- | --- | --- |
| `sealed` | `boolean` | `false` pour une version non scellée. Le bloc ne contient alors rien d'autre. |
| `sealed_at` | `string` | L'instant du scellement, au format ISO 8601. |
| `algorithm` | `string` | La version du calcul du sceau, `st-dpp-chain-v1` aujourd'hui. |
| `version_hash` | `string` ou `null` | Le maillon de cette version, 64 caractères hexadécimaux. |
| `prev_version_hash` | `string` ou `null` | Le maillon de la version précédente. `null` pour la première version d'un passeport. |
| `linked` | `boolean` | `true` quand la version porte un maillon. |
| `reason` | `string` | Présent uniquement quand `linked` vaut `false`, avec la valeur `sealed_before_chain`. La version a été publiée avant l'existence du chaînage, et aucun maillon n'est fabriqué après coup pour une publication que nous ne pouvons pas dater. |
| `chain_link_match` | `boolean` | Présent uniquement quand `linked` vaut `true`. Nous recalculons le maillon à partir du contenu servi et nous le comparons à celui qui est stocké. `false` veut dire que la version a été modifiée après sa publication. |

### Le bloc `vc`

L'attestation est le passeport rendu sous forme d'un document signé, au format
SD-JWT-VC. La clef privée de signature ne quitte pas un module matériel de
sécurité.

| Champ | Type | Description |
| --- | --- | --- |
| `issued` | `boolean` | `true` quand une attestation signée existe pour cette version. Toujours présent. |
| `vct` | `string` | L'identifiant du schéma de l'attestation, `https://schema.sealtrust.io/vct/digital-product-passport` par défaut. Absent quand la version n'en porte pas. |
| `issued_at` | `string` | L'instant d'émission, au format ISO 8601. Absent quand cet instant n'a pas été enregistré. |

### Pourquoi `ipfs_uri` peut manquer

Nous n'annonçons la copie IPFS que lorsque nous avons vérifié, au moment de
votre appel, que son contenu est exactement le passeport public que la marque
publie aujourd'hui. Nous allons chercher la copie, nous la hachons, et nous ne
publions le lien qu'en cas de correspondance.

Trois situations produisent donc une réponse sans `ipfs_uri` : aucune copie n'a
été épinglée, la copie n'a pas pu être récupérée au moment de votre appel, ou
son contenu ne correspond plus à ce que la marque publie. Une marque qui change
ses règles d'accès change ce que montre son passeport public, donc une copie
épinglée avant ce changement cesse d'être annoncée tant qu'elle n'a pas été
épinglée de nouveau. Le champ ne distingue pas ces trois cas. Ne lisez jamais
son absence comme une preuve que la copie a été altérée.

## Erreurs

Le corps d'une réponse d'erreur porte un champ `detail`.

| Code | Condition | Que faire |
| --- | --- | --- |
| 400 | Le dernier chiffre du GTIN envoyé n'est pas la clef de contrôle des chiffres qui le précèdent. C'est la seule cause de ce code sur ce point d'entrée : une valeur sans aucun chiffre, ou de plus de quatorze chiffres, renvoie 404 et non 400. `detail` vaut `Invalid GTIN: the check digit does not match.` | Recopiez le code imprimé sur le produit, chiffre pour chiffre, sans en ajouter ni en omettre. |
| 404 | La valeur envoyée n'est pas un GTIN exploitable : elle est vide, elle ne contient aucun chiffre, ou elle en contient plus de 14. `detail` vaut `Unknown GS1 Digital Link`. | Envoyez le GTIN tel qu'il est imprimé sur le produit, à 8, 12, 13 ou 14 chiffres. |
| 404 | Aucun modèle enregistré ne porte ce GTIN. `detail` vaut `Unknown GS1 Digital Link`. | Vérifiez le GTIN auprès de la marque. |
| 404 | Un modèle porte ce GTIN, mais aucun passeport de niveau référence n'est publié et visible publiquement pour lui. `detail` vaut `Unknown GS1 Digital Link`. | Ce n'est pas un échec de vérification. La marque n'a pas publié de passeport de référence pour cette référence commerciale, ou l'a réservé à un public restreint. Un passeport rattaché à un exemplaire n'est jamais servi ici. |
| 429 | Le plafond de 60 appels par 60 secondes est atteint pour votre adresse réseau, tous chemins `/passport` confondus. `detail` vaut `Rate limit exceeded: 60 requests per 60s`. | Attendez le nombre de secondes indiqué par `Retry-After`, puis réessayez. Mettez la réponse en cache de votre côté. |
| 500 | Une erreur inattendue s'est produite pendant le traitement de votre appel. `detail` vaut `Internal Server Error`. La réponse porte un en-tête `X-Request-Id`. | Réessayez. Si l'erreur persiste, contactez le support en indiquant la valeur de `X-Request-Id`. |

## Voir aussi

- [`GET /passport/01/{gtin}`](/reference/get-passport-gtin/),
  lire le passeport publié d'un modèle, à partir de son GTIN.
- [`GET /passport/{identifier}/proof`](/reference/get-passport-proof/),
  rassembler les preuves publiques du passeport d'un article.
- [`GET /01/{gtin}`](/reference/get-gs1-gtin/),
  résoudre un lien GS1 qui ne porte qu'un GTIN.
- [Confiance et preuves](/confiance-et-preuves/),
  ce que chaque preuve établit et comment un tiers refait la vérification.
