# Confiance et preuves

Ce que chaque preuve établit, où la lire, et comment un tiers refait la vérification de l'ancrage, de la copie IPFS et de la signature sans nous faire confiance.

Source : https://docs.sealtrust.io/confiance-et-preuves/

---

En quittant cette page, vous saurez quelles preuves accompagnent un produit et
son passeport, ce que chacune établit exactement, ce qu'aucune n'établit, et
comment un tiers refait la vérification lui-même, avec ses propres outils.

Toutes les adresses citées ici sont publiques et ne demandent aucune clef
d'API. Chacune existe sous deux formes, avec le préfixe `/v1` et sans préfixe.
Les exemples utilisent `/v1`, c'est la forme à préférer.

## Le principe

Une preuve n'a de valeur que si elle se refait sans son émetteur. Deux des
éléments décrits ici tiennent hors de nos serveurs : la copie IPFS et
l'ancrage.

La copie IPFS est adressée par son contenu : son adresse est le résultat du
calcul d'empreinte sur le fichier lui-même, donc modifier le fichier change
l'adresse.

L'ancrage est une inscription sur une chaîne publique, Base, `chain_id` 8453.
N'importe qui la lit sur un explorateur de blocs ou par un nœud.

Nous signons l'attestation avec une clef dont nous ne publions que la partie
publique. Vous vérifiez la signature contre cette clef publique. Par défaut,
nous publions cette clef sur notre API. Une marque qui pose son identifiant
d'émetteur sur son propre domaine sort aussi cette étape de nos serveurs.

Le reste, l'empreinte SHA-256 du contenu et le sceau de version, sont des
contrôles de cohérence que nous calculons. Ils sont utiles, et cette page dit
précisément jusqu'où ils portent.

## Ce que chaque preuve établit

| Preuve | Ce qu'elle établit | Ce qu'elle laisse ouvert |
| --- | --- | --- |
| Empreinte SHA-256 du contenu (`data_hash`) | le contenu enregistré correspond à l'empreinte enregistrée | les deux valeurs sont chez nous, c'est un contrôle de cohérence interne |
| Sceau de version (`seal`) | une version publiée puis modifiée se détache de la chaîne des versions suivantes | ne donne aucune date opposable à un tiers |
| Copie IPFS (`ipfs_uri`) | le document publié est figé : son adresse est son empreinte | ne dit rien de la date de publication |
| Ancrage du document (`passport_anchor`) | ce contenu de passeport existait au plus tard à la transaction | ne rend pas le contenu exact |
| Ancrage de l'article (`anchor`) | cet article appartient à un lot dont la racine est inscrite sur Base | ne dit rien du contenu du passeport |
| Attestation signée (`vc`) | le document a bien été émis par la marque annoncée | ne dit rien de l'exactitude des données décrites |

> [!ATTENTION] Deux ancrages, deux sujets
> Le bloc `anchor` date **l'article**. Le bloc `passport_anchor` date **le
> document**. Aucune inscription faite pour l'article ne porte sur le contenu du
> passeport. Une page qui annonce « passeport ancré sur Base » en s'appuyant sur
> le bloc `anchor` affirme plus que ce que la chaîne dit.

Les deux mécanismes ont la même forme et ne se croisent nulle part.

:::schema deux-ancrages
Deux colonnes séparées, une par mécanisme. À gauche, l'ancrage d'un lot
d'articles : la feuille d'un article réunit son identifiant de jeton,
l'empreinte de son identifiant d'unité et l'empreinte de l'adresse de ses
métadonnées. À droite, l'ancrage d'une version de passeport : la feuille réunit
l'identifiant du passeport, le numéro de version et l'empreinte SHA-256 du
contenu. De chaque côté, les feuilles forment un arbre, l'arbre donne une
racine, et cette racine part dans une transaction sur Base, `chain_id` 8453. La
première se lit sur `GET /v1/verify/merkle/{identifier}` et dans le bloc
`anchor`, la seconde dans le bloc `passport_anchor` de
`GET /v1/passport/{identifier}/proof`. Les deux colonnes ne se rejoignent
jamais : aucune inscription faite pour l'article ne porte sur le contenu du
passeport.
:::

La composition exacte de la feuille d'un article suit le contrat sur lequel son
lot a été frappé, et vous n'avez pas à la reconstituer : le point d'entrée
`GET /v1/verify/merkle/{identifier}` vous rend `leaf` et `proof` prêts à
l'emploi, et le contrat sait recalculer l'appartenance lui-même avec
`verifyAnchored`. La feuille d'une version de passeport, elle, est décrite champ
par champ plus bas, avec le code qui la recalcule.

## Où lire toutes les preuves d'un coup

Le point d'entrée est `GET /v1/passport/{identifier}/proof`. Il rassemble
l'empreinte, la copie IPFS, les deux ancrages, le sceau et l'état de
l'attestation.

Le champ `identifier` accepte l'empreinte d'UID (`0x` suivi de 64 caractères
hexadécimaux), l'identifiant de jeton (une suite de chiffres) ou le numéro de
série imprimé sur l'étiquette. Un numéro de certificat n'est pas résolu par
cette adresse et renvoie 404. Pour un numéro de certificat, passez par
`GET /v1/verify/merkle/{identifier}` ou par `/resolve`.

:::onglets
```bash title="curl"
curl "https://api.sealtrust.io/v1/passport/000000000000/proof"
```
```typescript
const reponse = await fetch(
  "https://api.sealtrust.io/v1/passport/000000000000/proof",
);
const preuves = await reponse.json();
console.log(preuves.anchor?.anchored, preuves.passport_anchor?.data_hash_matches);
```
```python
import requests

reponse = requests.get(
    "https://api.sealtrust.io/v1/passport/000000000000/proof",
    timeout=10,
)
reponse.raise_for_status()
preuves = reponse.json()
print(preuves.get("anchor", {}).get("anchored"))
print(preuves.get("passport_anchor", {}).get("data_hash_matches"))
```
:::

```json title="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": "0x5555555555555555555555555555555555555555555555555555555555555555",
    "basescan_url": "https://basescan.org/tx/0x5555555555555555555555555555555555555555555555555555555555555555",
    "merkle_root": "0x4444444444444444444444444444444444444444444444444444444444444444",
    "anchored": true,
    "proves": "batch_inclusion"
  },
  "passport_anchor": {
    "chain": "base",
    "chain_id": 8453,
    "tx_hash": "0x6666666666666666666666666666666666666666666666666666666666666666",
    "basescan_url": "https://basescan.org/tx/0x6666666666666666666666666666666666666666666666666666666666666666",
    "merkle_root": "0x7777777777777777777777777777777777777777777777777777777777777777",
    "leaf": "0x1111111111111111111111111111111111111111111111111111111111111111",
    "leaf_index": 0,
    "proof": [
      "0x2222222222222222222222222222222222222222222222222222222222222222",
      "0x3333333333333333333333333333333333333333333333333333333333333333"
    ],
    "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"
  }
}
```

Toutes les valeurs de cet exemple sont fictives.

Un passeport rattaché à un modèle a son propre résumé, à
`GET /v1/passport/01/{gtin}/proof`. Deux blocs y sont absents, et
cette absence est la réponse juste : `anchor` date un article, or un modèle n'en
est pas un, et `verifications` compte des vérifications d'UID, or un modèle n'a
pas d'UID. La réponse porte alors `"level": "model"` et le GTIN.

## Vérifier la copie IPFS

Nous déposons la copie sur IPFS au moment de la publication. Depuis le passage
au dépôt de la **projection publique**, cette copie ne contient que les champs
du niveau public. Pour les passeports plus anciens, nous ne servons le lien que
si le contenu déposé correspond à cette projection.

Deux choses sont à votre portée.

**Récupérer la copie.** Le champ `ipfs_gateway_url` donne un lien direct. Le
champ `ipfs_uri` donne l'identifiant de contenu, que vous pouvez ouvrir par la
passerelle de votre choix ou par votre propre nœud IPFS. Ne dépendez pas de la
passerelle que nous indiquons.

**Comparer dans le temps.** L'identifiant de contenu est le résultat du calcul
d'empreinte sur le fichier. Deux récupérations du même identifiant rendent les
mêmes octets, sinon l'identifiant aurait changé.

L'identifiant vous permet de prouver plus tard que la copie que vous détenez est
bien celle qui était publiée. Il ne garantit pas que le fichier sera encore
servi par une passerelle. Nous n'annonçons aucune durée de conservation.
Conservez l'identifiant de contenu et une copie des octets que vous avez lus.

> [!INFO] Pourquoi le lien est parfois absent
> Le lien IPFS est retiré dans deux cas : la copie récupérée ne correspond pas à
> la projection publique attendue, ou aucune passerelle n'a répondu. Nous ne
> servons un lien que lorsque nous avons pu vérifier son contenu. Une passerelle
> injoignable suffit donc à faire disparaître le lien d'une réponse à l'autre.
> Notez l'identifiant de contenu le jour où vous l'obtenez.

Le point d'entrée `GET /v1/passport/{identifier}/verify` fait cette comparaison
pour vous et rend `ipfs_match`. Trois valeurs, trois sens : `true` la copie
correspond, `false` elle diffère, `null` elle n'a pas pu être récupérée. Une
passerelle injoignable donne `null`, jamais `false`.

```bash title="curl"
curl "https://api.sealtrust.io/v1/passport/000000000000/verify"
```

La même réponse porte `db_hash_match`, qui compare une donnée que nous détenons
à une empreinte que nous détenons, et le bloc `seal`, dont `chain_link_match` à
`false` signale qu'une version scellée a été modifiée après publication.

## Vérifier l'ancrage

L'ancrage repose sur un arbre de Merkle. Nous réduisons chaque document à dater
à une empreinte, appelée feuille. Nous combinons les feuilles deux à deux jusqu'à
une valeur unique, la racine. Nous inscrivons la racine seule sur la chaîne. Une
preuve d'appartenance vous donne la liste des empreintes voisines qui vous
permettent de remonter d'une feuille jusqu'à la racine.

Vous n'avez pas à nous faire confiance pour conclure. Les valeurs de départ
viennent de notre réponse, et la comparaison finale se fait contre la chaîne, que
nous ne contrôlons pas. Notez ces valeurs le jour où vous les lisez : elles
suffisent ensuite à refaire la vérification sans nous rappeler.

### Étape 1 : recalculer la racine

Prenez `leaf` et `proof` dans le bloc `passport_anchor`. À chaque étape, les
deux valeurs sont rangées dans l'ordre croissant avant d'être concaténées et
hachées en keccak256, la fonction d'empreinte utilisée par la chaîne. Le
résultat final doit égaler `merkle_root`.

Les deux exemples ci-dessous demandent une bibliothèque qui calcule keccak256 :
`eth-utils` en Python, `ethers` en TypeScript.

:::onglets
```python
from eth_utils import keccak


def racine_depuis_preuve(feuille: str, preuve: list[str]) -> str:
    courant = bytes.fromhex(feuille[2:])
    for voisin in preuve:
        autre = bytes.fromhex(voisin[2:])
        gauche, droite = (courant, autre) if courant < autre else (autre, courant)
        courant = keccak(gauche + droite)
    return "0x" + courant.hex()


print(
    racine_depuis_preuve(
        "0x1111111111111111111111111111111111111111111111111111111111111111",
        [
            "0x2222222222222222222222222222222222222222222222222222222222222222",
            "0x3333333333333333333333333333333333333333333333333333333333333333",
        ],
    )
)
```
```typescript
import { keccak256 } from "ethers";

function racineDepuisPreuve(feuille: string, preuve: string[]): string {
  let courant = feuille.toLowerCase();
  for (const voisin of preuve) {
    const autre = voisin.toLowerCase();
    courant =
      courant < autre
        ? keccak256("0x" + courant.slice(2) + autre.slice(2))
        : keccak256("0x" + autre.slice(2) + courant.slice(2));
  }
  return courant;
}

console.log(
  racineDepuisPreuve(
    "0x1111111111111111111111111111111111111111111111111111111111111111",
    [
      "0x2222222222222222222222222222222222222222222222222222222222222222",
      "0x3333333333333333333333333333333333333333333333333333333333333333",
    ],
  ),
);
```
:::

### Étape 2 : comparer la racine à la chaîne

Ouvrez `basescan_url`. La transaction émet un événement `RootAnchored`, qui
porte quatre valeurs : l'identifiant de lot, la racine, le nombre de feuilles et
une empreinte de métadonnées. Comparez la racine de l'événement à celle que vous
venez de recalculer.

Si les deux correspondent, la racine que nous vous avons servie est bien celle
inscrite sur Base, à la date du bloc qui contient la transaction. Cette date est
le seul élément que la chaîne ajoute, et c'est celui que ni l'empreinte ni la
copie IPFS ne peuvent donner.

### Étape 3 : relier la racine au contenu

La feuille d'une version de passeport est l'empreinte keccak256 de trois
valeurs encodées au format ABI, la mise en forme binaire attendue par un contrat
sur la chaîne : `passport_id` en `uint256`, `passport_version` en `uint256`, et
`data_hash` en `bytes32`.

`data_hash` est rendu sans préfixe `0x` : lisez-le comme 32 octets hexadécimaux.

:::onglets
```python
from eth_abi import encode
from eth_utils import keccak

passport_id = 7
passport_version = 3
data_hash = "0000000000000000000000000000000000000000000000000000000000000000"

feuille = "0x" + keccak(
    encode(
        ["uint256", "uint256", "bytes32"],
        [passport_id, passport_version, bytes.fromhex(data_hash)],
    )
).hex()
print(feuille)
```
```typescript
import { AbiCoder, keccak256 } from "ethers";

const passportId = 7;
const passportVersion = 3;
const dataHash =
  "0000000000000000000000000000000000000000000000000000000000000000";

const feuille = keccak256(
  AbiCoder.defaultAbiCoder().encode(
    ["uint256", "uint256", "bytes32"],
    [passportId, passportVersion, "0x" + dataHash],
  ),
);
console.log(feuille);
```
:::

Les valeurs de cet exemple sont fictives. Comparez la feuille que vous obtenez
au champ `leaf` du bloc `passport_anchor`.

`passport_version` figure dans le résumé des preuves. `data_hash` aussi.
`passport_id` est le champ `id` de la réponse de
`GET /v1/passport/{identifier}`, ou de `GET /v1/passport/01/{gtin}` pour un
passeport de modèle. Ces deux réponses sont publiques et existent dès qu'une
version est publiée. Le numéro de version fait partie de la feuille pour qu'une
preuve désigne une version précise, même si deux versions portaient des données
identiques.

Le champ `data_hash_matches` dit si l'empreinte enregistrée aujourd'hui est
celle qui a été inscrite. La valeur `false` signifie que les données
enregistrées ne correspondent plus à ce qui a été ancré. C'est le signal que ce
mécanisme existe pour lever, et il est publié.

> [!ATTENTION] Ce que vous ne pouvez pas recalculer seul
> `data_hash` couvre l'intégralité des données de la version, y compris les
> champs que votre niveau d'accès ne reçoit pas. Sans ces données complètes, vous
> vérifiez que l'empreinte publiée est bien celle qui a été ancrée, et vous ne
> pouvez pas relier cette empreinte au texte que vous lisez. La copie IPFS
> couvre cette portée-là : elle est la projection publique, et elle est figée.

## Vérifier l'appartenance d'un article à un lot ancré

Le point d'entrée est `GET /v1/verify/merkle/{identifier}`. Il rend la preuve
d'appartenance d'un article au lot dont la racine a été inscrite sur Base. Ici,
`identifier` accepte l'empreinte d'UID, l'identifiant de jeton, le numéro de
série imprimé sur l'étiquette ou un numéro de certificat.

```bash title="curl"
curl "https://api.sealtrust.io/v1/verify/merkle/000000000000"
```

```json title="200 OK"
{
  "anchor_batch_id": 42,
  "merkle_root": "0x4444444444444444444444444444444444444444444444444444444444444444",
  "leaf": "0x1111111111111111111111111111111111111111111111111111111111111111",
  "leaf_index": 0,
  "proof": [
    "0x2222222222222222222222222222222222222222222222222222222222222222",
    "0x3333333333333333333333333333333333333333333333333333333333333333"
  ],
  "anchor_tx_hash": "0x5555555555555555555555555555555555555555555555555555555555555555",
  "basescan_url": "https://basescan.org/tx/0x5555555555555555555555555555555555555555555555555555555555555555",
  "contract_address": "0x0000000000000000000000000000000000000000",
  "network": "Base",
  "token_id": "1",
  "verified": true,
  "lifecycle_status": "mined",
  "withdrawn": false,
  "leaf_set": "frozen"
}
```

La preuve se recalcule avec la même fonction que plus haut. Deux vérifications
supplémentaires sont possibles sur la chaîne, sur le contrat dont l'adresse est
donnée par `contract_address`.

| Lecture sur le contrat | Ce qu'elle rend |
| --- | --- |
| `anchoredRootByBatch(anchor_batch_id)` | la racine inscrite pour ce lot, à comparer à `merkle_root` |
| `verifyAnchored(anchor_batch_id, token_id, proof)` | un booléen : la chaîne recalcule elle-même l'appartenance |

Les deux fonctions sont en lecture seule et ouvertes à tous. Le champ `verified`
de la réponse est le résultat de cette même lecture faite par nos soins. Il vaut
`null` quand la lecture n'a pas pu aboutir, ce qui n'enlève rien à la preuve
locale.

Trois champs demandent une lecture attentive.

`leaf_set` vaut `frozen` quand l'ensemble des feuilles utilisé est celui qui a
été enregistré au moment de l'ancrage. Il vaut `recomputed` pour les lots
ancrés avant l'existence de cet enregistrement : l'arbre est alors reconstruit à
partir des lignes actuelles du lot, et une modification ultérieure du lot peut
le faire diverger de la racine inscrite.

`lifecycle_status` et `withdrawn` décrivent l'état de l'article aujourd'hui. Un
ancrage affirme qu'un ensemble de feuilles donnait cette racine ce jour-là, et
cela reste vrai quoi qu'il arrive ensuite. La preuve d'un article retiré du
catalogue continue donc de se vérifier. Ces deux champs existent pour que vous
n'ayez pas à deviner l'état courant à partir d'une preuve valide.

> [!INFO] Un 404 ici est le cas normal
> La plupart des articles n'appartiennent pas à un lot ancré. Ce point d'entrée
> répond alors 404. Traitez cette réponse comme « cet article ne fait pas partie
> d'un lot ancré », jamais comme un échec de vérification.

## Vérifier la signature de l'attestation

La publication d'un passeport déclenche l'émission d'une attestation signée, au
format SD-JWT-VC. Elle établit que le document vient de la marque annoncée. Si
l'émission échoue, la publication aboutit quand même et le passeport reste sans
attestation.

Le chemin qui ne dépend pas de nous tient en quatre gestes.

**Récupérez l'attestation** avec `GET /v1/passport/{identifier}/vc`. La réponse
porte `sd_jwt_vc`, la présentation elle-même, et `issuer`, l'identifiant de
l'émetteur au format `did:web`.

**Récupérez le document de l'émetteur.** Un identifiant de la forme
`did:web:<domaine>` se résout à `https://<domaine>/.well-known/did.json`. Un
identifiant de la forme `did:web:<hote>:brand:<numero>` se résout à
`https://<hote>/brand/<numero>/did.json`. Le document liste les clefs publiques
non révoquées de la marque, chacune au format `JsonWebKey2020`.

**Choisissez la bonne clef.** L'en-tête de l'attestation porte un champ `kid` de
la forme `<did>#key-<version>`. Prenez la clef qui porte cet identifiant dans le
document. Cette numérotation permet à une attestation ancienne de rester
vérifiable après une rotation de clef, tant que l'ancienne clef n'est pas
révoquée.

**Vérifiez la signature.** L'algorithme est `ES256`. 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 convient.

```bash title="curl"
curl "https://api.sealtrust.io/v1/passport/000000000000/vc"
```

Une marque peut porter son identifiant d'émetteur sur son propre domaine. Elle
conserve alors la propriété de son identité d'émetteur, et la vérification de
ses attestations ne passe plus par nos serveurs.

Si vous préférez une réponse directe, `GET /v1/passport/{identifier}/vc/verify`
fait la vérification et rend `verified`. En cas d'échec, `verified` vaut `false`
et `error` vaut `verification_failed`, sans le message d'origine.

> [!INFO] Où vit la clef privée
> La clef privée de signature n'est jamais rendue par l'API. Nous ne publions
> que la partie publique, celle que porte le document de l'émetteur, et c'est
> elle qui vous sert à vérifier.

## Quand une preuve est absente

Une preuve absente est une information. Nous la rendons telle quelle.

| Situation | Ce que vous observez | Lecture juste |
| --- | --- | --- |
| Version publiée il y a peu | `passport_anchor` absent | l'ancrage d'un passeport est une opération que SealTrust déclenche à la main, et beaucoup de passeports ne sont jamais ancrés. Ne l'attendez pas. Le sceau de version tient l'intégrité sans lui |
| Lot de l'article jamais ancré | `anchor.anchored` à `false`, `type` à `mint_transaction`, `proves` à `token_minted` | la transaction prouve que le jeton existe. Elle ne dit rien du lot ni du passeport |
| Article jamais inscrit sur la chaîne | bloc `anchor` absent en entier | l'article n'a ni lot ancré ni transaction de frappe. Lisez `passport_anchor`, qui date le document et ne dépend pas de l'article |
| Dépôt IPFS échoué à la publication | `ipfs_uri` absent | la publication a abouti quand même, le passeport reste sans copie IPFS |
| Passerelle IPFS injoignable | `ipfs_match` à `null`, `ipfs_uri` et `ipfs_gateway_url` absents | inconnu. Réessayez plus tard, ou par une autre passerelle |
| Passeport publié avant l'existence de la chaîne de versions | `seal.linked` à `false`, `reason` à `sealed_before_chain` | aucun maillon n'est fabriqué après coup |
| Aucune attestation émise | `vc.issued` à `false` | republiez le passeport pour déclencher l'émission |
| Lot modifié depuis son ancrage | `GET /v1/verify/merkle/{identifier}` répond 409 | la racine recalculée diffère de la racine inscrite. Nous refusons de servir une preuve qui échouerait sur la chaîne |

## Plafonds d'appels et erreurs

Les adresses commençant par `/passport` partagent un plafond de 60 appels par
tranche de 60 secondes et par adresse IP. Celles commençant par `/verify`
partagent un plafond de 30 appels par tranche de 60 secondes et par adresse IP.
Les formes avec et sans `/v1` comptent sur le même compteur.

Un dépassement renvoie 429, avec les en-têtes `Retry-After`, `X-RateLimit-Limit`,
`X-RateLimit-Remaining` et `X-RateLimit-Reset`. Ces trois derniers accompagnent
aussi les réponses qui passent. Lisez `X-RateLimit-Remaining` pour espacer vos
appels avant d'atteindre le plafond.

Toute réponse porte `Cache-Control: no-store, max-age=0`, y compris les résumés
de preuves `/proof` et `/verify/merkle`. Ne placez aucune de ces réponses
derrière un 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.

| Code | Condition | Que faire |
| --- | --- | --- |
| 404 | aucun produit ne correspond à l'identifiant | vérifiez l'identifiant |
| 404 | le produit existe, aucun passeport publié | publiez une version |
| 404 | l'article n'appartient à aucun lot ancré, sur `/verify/merkle` | cas normal, ne le lisez pas comme un échec |
| 404 | aucune attestation émise pour ce passeport, sur `/vc` | republiez le passeport pour déclencher l'émission |
| 409 | le lot a changé depuis son ancrage, sur `/verify/merkle` | la preuve serait invalide sur la chaîne, contactez la marque |
| 429 | plafond d'appels dépassé | attendez la durée indiquée par `Retry-After` |

## Ce qu'il faut retenir

Chaque preuve établit une chose et une seule, et la réponse la nomme. Lisez
`anchored`, `proves`, `data_hash_matches`, `chain_link_match` et `ipfs_match`
avant d'affirmer quoi que ce soit.

Ce qui ne dépend pas de nous : la copie IPFS, adressée par son contenu, et
l'inscription sur Base `chain_id` 8453, lisible par tous. La signature de
l'attestation se vérifie contre une clef publique. Par défaut, nous publions
cette clef sur notre API. Une marque qui pose son identifiant d'émetteur sur son
propre domaine sort aussi cette vérification de nos serveurs.

Ce qui dépend de nous : l'empreinte enregistrée et le sceau de version. Ce sont
des contrôles de cohérence utiles, et cette page en dit la portée exacte.

Un ancrage établit une date. Il n'établit pas que le contenu daté est exact.
Il n'empêche pas une marque de
publier une correction, qui devient la version suivante et laisse la version
ancrée vérifiable.
