# GET /passport/{identifier}

Lire le passeport numérique publié d'un produit à partir de son numéro imprimé, de son identifiant de jeton ou de son empreinte de puce, au niveau d'accès demandé. Point d'entrée public.

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

---

Vous lisez le passeport numérique publié d'un seul produit. En quittant cette
page, vous saurez récupérer ses données au niveau d'accès que vous demandez,
lire sa garantie, savoir sur quelle base chaque section peut être crue, et
reconnaître un produit retiré du catalogue.

Adresse complète :

```http
GET https://api.sealtrust.io/v1/passport/{identifier}
```

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

## Autorisation

Aucune pour les niveaux `public` et `end_user`. Ce point d'entrée est public.

Une clef d'API partenaire n'ouvre rien ici. Le jeton reçu dans l'en-tête
`Authorization` est décodé comme un jeton de session de compte utilisateur, et
une clef d'API n'en est pas un : la lecture échoue en silence et l'appel se
poursuit comme un appel anonyme.

Quatre valeurs du paramètre `access_tier` exigent en revanche une session de
compte, présentée par l'en-tête `Authorization: Bearer <jeton de session>` ou
par le cookie de session posé à la connexion.

| Niveau demandé | Ce qu'il faut |
| --- | --- |
| `public` | rien |
| `end_user` | rien |
| `repairer` | une session, et une accréditation de réparateur sur la marque du produit |
| `recycler` | une session, et une accréditation de recycleur sur la marque du produit |
| `upstream` | une session ayant accès à la marque du produit, ou le rôle d'autorité |
| `authority` | une session portant le rôle d'autorité de surveillance du marché |

Une session ayant accès à la marque du produit ouvre les trois niveaux
de métier sur ses propres produits. Une session portant le rôle d'autorité
de surveillance du marché les ouvre également.

> [!INFO] Le propriétaire actuel du produit voit plus
> Si l'appel porte une session valide et que le compte est le propriétaire
> actuel de l'unité, deux choses changent. Une demande au niveau `public` est
> servie au niveau `end_user`. Les passeports dont la visibilité est
> `owner_only` deviennent visibles. Le champ `is_owner` de la réponse JSON par
> défaut et l'en-tête `X-DPP-Access-Tier` signalent ce basculement.

## Plafond d'appels

60 appels par tranche de 60 secondes, comptés par adresse IP appelante. La
fenêtre est fixe. Ce plafond est partagé par toutes les adresses qui commencent
par `/passport`, et les formes `/passport/…` et `/v1/passport/…` alimentent le
même compteur.

Chaque réponse acceptée porte trois en-têtes qui décrivent ce compteur.

| 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 dépassement renvoie 429, avec les mêmes trois en-têtes et un `Retry-After`
en secondes.

> [!INFO] Cet appel ne consomme aucun quota
> Le quota quotidien d'une clef d'API n'est pas entamé par cet appel, et le
> quota mensuel de produits de votre offre non plus. Ce point d'entrée
> n'interroge ni l'un ni l'autre.

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

| Nom | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `identifier` | `string` | oui | L'identifiant du produit. Trois formes sont acceptées, voir ci-dessous. |
| `access_tier` | `string` | non | Le niveau d'accès demandé. Vaut `public` par défaut. Six valeurs acceptées, listées plus bas. |
| `verify_integrity` | `boolean` | non | Vaut `false` par défaut. À `true`, le serveur récupère la copie IPFS du passeport, compare son empreinte, et ajoute un bloc `integrity` à la réponse. |
| `format` | `string` | non | Absent par défaut, le serveur rend alors le JSON décrit plus bas. La valeur `jsonld` rend le même contenu filtré, exprimé en Schema.org et GS1. Le serveur ignore toute autre valeur et rend la réponse par défaut. |

### Les trois formes d'identifiant acceptées

| Forme | À quoi elle ressemble | Où vous la trouvez |
| --- | --- | --- |
| Numéro de série | 12 caractères, chiffres et lettres majuscules. Les lettres I, L, O et U n'y figurent jamais. | Imprimé sur le produit, c'est ce que porte son QR |
| Identifiant de jeton | Une suite de chiffres, souvent très longue | Rendu par nos réponses dans le champ `token_id` |
| Empreinte de puce | `0x` suivi de 64 caractères hexadécimaux | Rendue par nos réponses dans le champ `uid_hash` |

Le serveur reconnaît l'empreinte de puce quelle que soit la casse. Il
reconnaît le numéro de série de la même façon, et il le canonicalise comme le
fait le résolveur du QR : il lit les lettres `I` et `L` comme un `1`, et la
lettre `O` comme un `0`. Vous pouvez donc recopier à la main le numéro lu sur
une étiquette, même si vous confondez ces caractères.

Le serveur reconnaît la forme à l'écriture. Il cherche une valeur qui commence
par `0x` et fait exactement 66 caractères comme une empreinte de puce. Il
cherche toute autre valeur d'abord comme un identifiant de jeton, puis, si
cette recherche ne donne rien, comme un numéro de série.

### Les six valeurs de `access_tier`

Ces niveaux sont des publics différents, sans hiérarchie entre eux. Un
recycleur n'est pas au-dessus d'un réparateur. Chacun des trois niveaux
de métier hérite du niveau public et du niveau utilisateur final, puis
ajoute ce que son métier demande.

| Valeur | Ce qu'elle ajoute aux champs de `data` |
| --- | --- |
| `public` | identification du produit, conformité ESPR, REACH et marquage CE, pourcentage de recyclabilité, pourcentage de contenu recyclé, étiquettes, spécification générale de batterie |
| `end_user` | impact environnemental, circularité complète, matière principale, coton biologique certifié, durabilité, efficacité énergétique, empreinte carbone |
| `repairer` | nomenclature, notice de démontage, indice de réparabilité, état de santé de batterie |
| `recycler` | composition matière, substances préoccupantes, notice de démontage, état de santé de batterie |
| `upstream` | composition matière, substances préoccupantes, fabrication, chaîne d'approvisionnement |
| `authority` | l'intégralité des données, sans filtrage |

Une marque peut remplacer ces règles par les siennes, par catégorie de produit.
Le tableau ci-dessus décrit ce qui s'applique à défaut de règles propres à la
marque.

Le serveur refuse toute autre valeur en 422. Il renvoie le niveau réellement
servi dans le champ `access_tier` et dans l'en-tête `X-DPP-Access-Tier` de la
réponse JSON par défaut. Lisez l'un des deux plutôt que de le supposer. Avec
`format=jsonld`, ni ce champ ni cet en-tête n'existent, voir plus bas.

### En-têtes de réponse à connaître

| En-tête | Contenu |
| --- | --- |
| `X-DPP-Access-Tier` | le niveau réellement servi |
| `Cache-Control` | `no-store, max-age=0`, quel que soit le niveau servi. Ne placez cette réponse derrière aucun cache partagé. |

Le serveur ne pose `X-DPP-Access-Tier` que sur la réponse JSON par défaut.
`Cache-Control` porte la même valeur sur les deux formats.

Aucun en-tête n'est obligatoire dans la requête.

## Corps de la requête

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

## Requête d'exemple

Lecture du passeport public du produit dont le numéro imprimé est
`EXEMP1E00001`. Les trois exemples font le même appel, arrêtent le programme
sur une réponse d'erreur, puis affichent les trois mêmes valeurs :
`passport_version`, `access_tier` et `data.product_identity`.

:::onglets
```bash title="curl"
curl --fail-with-body -s \
  "https://api.sealtrust.io/v1/passport/EXEMP1E00001?access_tier=public" \
  | jq '{passport_version, access_tier, product_identity: .data.product_identity}'
```
```typescript
const identifiant = "EXEMP1E00001";

const url = new URL(
  `https://api.sealtrust.io/v1/passport/${encodeURIComponent(identifiant)}`,
);
url.searchParams.set("access_tier", "public");

const response = await fetch(url, { method: "GET" });

if (!response.ok) {
  throw new Error(`SealTrust a répondu ${response.status}`);
}

const passeport = await response.json();

console.log({
  passport_version: passeport.passport_version,
  access_tier: passeport.access_tier,
  product_identity: passeport.data.product_identity,
});
```
```python
import requests
from urllib.parse import quote

identifiant = "EXEMP1E00001"

response = requests.get(
    f"https://api.sealtrust.io/v1/passport/{quote(identifiant, safe='')}",
    params={"access_tier": "public"},
    timeout=30,
)
response.raise_for_status()

passeport = response.json()

print(
    {
        "passport_version": passeport["passport_version"],
        "access_tier": passeport["access_tier"],
        "product_identity": passeport["data"]["product_identity"],
    }
)
```
:::

Dans l'exemple `curl`, `--fail-with-body` renvoie un code de sortie non nul
quand le serveur répond une erreur, et affiche quand même le corps. L'outil
`jq` ne sert qu'à lire le JSON dans le terminal, il ne participe pas à l'appel.

> [!INFO] Le SDK TypeScript ne couvre pas ce point d'entrée
> `@sealtrust-io/sdk` n'expose aucune méthode pour cette adresse. L'exemple
> ci-dessus utilise `fetch`, disponible sans dépendance.

## Réponse d'exemple

Code HTTP `200`.

Ce produit est encore au catalogue, il n'a pas été réclamé par un client, et le
passeport est demandé au niveau public.

```json
{
  "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"
    },
    "compliance": {
      "eu_espr": true,
      "reach": true,
      "ce_marking": false
    },
    "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.

### Les champs de la réponse

| Champ | Type | Description |
| --- | --- | --- |
| `id` | `integer` | L'identifiant de la version de passeport servie. |
| `product_id` | `integer` | L'unité à laquelle ce passeport est attaché, ou `null` quand le passeport porte sur le modèle et vaut pour tous ses exemplaires. |
| `brand_id` | `integer` | Le numéro de la marque à laquelle le passeport appartient. |
| `schema_version` | `string` | La version du schéma de données du passeport. |
| `passport_version` | `integer` | Le numéro de version publiée. Il augmente à chaque nouvelle publication. |
| `data` | `object` | Les données du passeport, filtrées selon le niveau servi. Sa forme dépend de la catégorie de produit. |
| `data_hash` | `string` ou `null` | L'empreinte des données complètes de cette version, 64 caractères hexadécimaux. `null` quand aucune empreinte n'a été enregistrée pour cette version. |
| `ipfs_uri` | `string` | L'adresse IPFS de la copie du passeport. Toujours `null` aux niveaux `public` et `end_user`. |
| `ipfs_gateway_url` | `string` | L'adresse HTTP par laquelle cette copie se lit. Toujours `null` aux niveaux `public` et `end_user`. |
| `visibility` | `string` | La visibilité de la version servie : `public`, ou `owner_only` quand le propriétaire actuel est authentifié. La visibilité `brand_only` n'est jamais servie ici. |
| `access_tier` | `string` | Le niveau réellement servi, qui peut différer du niveau demandé pour le propriétaire du produit. |
| `is_owner` | `boolean` | `true` quand l'appel est authentifié et que le compte est le propriétaire actuel de l'unité. |
| `published_at` | `string` | Date et heure de publication de cette version, au format ISO 8601, ou `null`. |
| `product_name` | `string` ou `null` | Le nom du produit. `null` quand aucun nom n'a été enregistré sur l'article. |
| `brand_name` | `string` | Le nom de la marque, ou `null` si le produit n'est rattaché à aucune. |
| `image_url` | `string` | La photographie du modèle, ou `null`. |
| `gtin` | `string` | Le GTIN du modèle, ramené à 14 chiffres. `null` quand le modèle n'en porte pas, ou quand la valeur enregistrée n'est pas un GTIN valide. |
| `gs1_digital_link` | `string` | Le lien GS1 Digital Link qui identifie cet exemplaire, de la forme `<domaine de résolution>/01/<gtin sur 14 chiffres>/21/<numéro de série>`. `null` quand le GTIN ou le numéro de série manque. |
| `warranty` | `object` | Le résumé de garantie, ou `null` quand le produit n'en a pas. Voir ci-dessous. |
| `evidence` | `object` | Sur quelle base chaque section peut être crue. Voir ci-dessous. Absent si son calcul échoue. |
| `lifecycle` | `object` | Présent uniquement quand l'unité est détruite ou sortie du catalogue. Voir ci-dessous. |
| `integrity` | `object` | Présent uniquement quand `verify_integrity=true` et que le lien IPFS est servi à votre niveau. Voir ci-dessous. |

### Le bloc `warranty`

| Champ | Type | Description |
| --- | --- | --- |
| `status` | `string` | `active`, `expiring_soon`, `expired` ou `void`. Recalculé à chaque lecture. |
| `ends_at` | `string` | Date de fin, au format ISO 8601, ou `null` pour une garantie à vie. |
| `duration_months` | `integer` | La durée annoncée, en mois. |
| `transferable` | `boolean` | `true` quand la garantie suit le produit lors d'un changement de propriétaire. |
| `remaining_days` | `integer` | Jours entiers restants. Négatif quand la garantie est passée. `null` pour une garantie à vie ou annulée. |

### Le bloc `evidence`

Trois valeurs existent, et elles sont calculées par SealTrust. Une marque ne
peut pas les choisir.

| Valeur | Ce qu'elle dit |
| --- | --- |
| `verified` | Vérifié mécaniquement contre un registre public, sans déclaration de personne. |
| `document_backed` | Un document tiers est joint et peut être récupéré. Son contenu n'a pas été audité par SealTrust. |
| `declared` | Déclaré par la marque. Enregistré, daté, attribuable, non vérifié de façon indépendante. |

Quatre sections portent une de ces valeurs : `identity`, `integrity`,
`composition` et `substances_of_concern`. La section `identity` passe à
`verified` quand l'unité porte un identifiant de jeton 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 bloc porte en plus
`legend`, qui redit le sens des valeurs présentes, `derived` à `true`, et
`note`.

### Le bloc `lifecycle`

Il n'apparaît que si l'unité est détruite ou sortie du catalogue. Son passeport
reste servi pour que l'identifiant continue de résoudre.

```json
{
  "lifecycle": {
    "status": "superseded",
    "is_burned": false,
    "superseded": true,
    "note": "This unit is superseded or withdrawn; its passport is retained so the identifier stays resolvable (EN 18219 §4.2.2 persistence)."
  }
}
```

Lisez `is_burned` avant `status`.

Le champ `status` vaut `superseded` quand l'unité a été remplacée par une autre,
et `archived` quand elle a été retirée sans remplacement. Le champ `superseded`
ne vaut `true` que pour le premier cas.

Le bloc apparaît aussi quand l'unité a été détruite, c'est-à-dire quand
`is_burned` vaut `true`. Dans ce cas `status` porte l'état courant du produit,
qui peut être `null` ou une valeur active. Ne déduisez donc jamais la
destruction de la valeur de `status`.

### Le bloc `integrity`

```json
{
  "integrity": {
    "ipfs_fetched": true,
    "ipfs_hash": "1111111111111111111111111111111111111111111111111111111111111111",
    "expected_hash": "1111111111111111111111111111111111111111111111111111111111111111",
    "match": true
  }
}
```

| Champ | Type | Description |
| --- | --- | --- |
| `ipfs_fetched` | `boolean` | `true` quand le serveur a réussi à lire la copie IPFS. |
| `ipfs_hash` | `string` | L'empreinte du contenu réellement lu sur IPFS. |
| `expected_hash` | `string` | L'empreinte attendue pour ce contenu. |
| `match` | `boolean` | Le verdict de la comparaison. |

`expected_hash` est l'empreinte de la projection PUBLIQUE du passeport, celle
qui est déposée sur IPFS. Elle diffère de `data_hash`, qui couvre les données
complètes, y compris les champs réservés aux niveaux professionnels. Les deux
valeurs coïncident seulement quand le passeport ne porte aucun champ non
public. Ne comparez donc jamais `expected_hash` et `data_hash`.

Le champ `match` vaut `true` quand la copie IPFS correspond, `false` quand elle
diffère, et `null` quand la copie n'a pas pu être récupérée. Dans ce dernier cas
`ipfs_fetched` vaut `false`, un champ `error` remplace les deux empreintes, et
`null` signifie que rien n'a pu être conclu.

> [!ATTENTION] `verify_integrity=true` ne fait rien aux niveaux ouverts
> Le serveur ne calcule ce bloc que s'il vous sert le lien IPFS. Aux niveaux
> `public` et `end_user`, il ne sert jamais ce lien. Il accepte donc le
> paramètre, et il rend une réponse sans bloc `integrity`. Aucune erreur ne
> vous le signale.

### La réponse en JSON-LD

Avec `format=jsonld`, la réponse porte le type de contenu
`application/ld+json`. C'est un document Schema.org et GS1 dont les champs sont
filtrés par le même niveau d'accès. Il ne contient ni `passport_version`, ni
`data_hash`, ni les blocs `warranty`, `evidence`, `lifecycle` et `integrity`
décrits ci-dessus : la garantie y est exprimée en `WarrantyPromise`, et les
autres blocs n'y figurent pas.

Deux autres différences comptent pour votre intégration.

Le document ne porte pas de champ `access_tier`. Le serveur ne renvoie pas non
plus l'en-tête `X-DPP-Access-Tier`. Pour connaître le niveau réellement servi,
appelez sans `format`, ou tenez-vous-en au niveau que vous avez demandé.

Le serveur ignore `verify_integrity` dans ce format. Il rend le document JSON-LD
avant de calculer le bloc `integrity`, donc ce paramètre ne change rien à la
réponse et aucune erreur ne vous le signale.

## Erreurs

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

| Code | Condition | Que faire |
| --- | --- | --- |
| 401 | `access_tier=authority` est demandé sans session valide. `detail` vaut `Authority-tier access requires authentication`. | Connectez-vous avec un compte portant le rôle d'autorité de surveillance du marché. Une clef d'API partenaire ne convient pas. |
| 401 | `access_tier` vaut `repairer`, `recycler` ou `upstream`, et l'appel ne porte aucune session valide. `detail` vaut `Professional-tier access requires authentication`. | Présentez un jeton de session de compte, ou demandez le niveau `public` ou `end_user`. |
| 403 | `access_tier=authority` est demandé par un compte connecté qui ne porte pas ce rôle. `detail` vaut `Authority-tier access is restricted to market surveillance authorities`. | Demandez le niveau qui correspond à votre habilitation. |
| 403 | Un niveau professionnel est demandé par un compte connecté qui n'a ni accès à la marque du produit, ni l'accréditation correspondante sur cette marque. `detail` vaut `This tier is restricted to the product's brand, partners holding the matching accreditation, and market-surveillance authorities`. | Demandez à la marque l'accréditation qui correspond à votre métier, puis demandez le niveau de ce métier. |
| 404 | Aucun produit ne correspond à l'identifiant, sous aucune des trois formes acceptées. `detail` vaut `Product not found`. | Vérifiez le numéro recopié. Un produit détruit ou retiré du catalogue reste résolu ici, donc cette réponse veut bien dire que l'identifiant est inconnu. |
| 404 | Le produit existe, mais aucune version de passeport publiée ne lui correspond. `detail` vaut `No published passport found for this product`. | La marque doit publier une version. Un brouillon non publié n'est jamais servi, et une version en visibilité `brand_only` non plus. |
| 422 | Une valeur de paramètre est refusée : un `access_tier` qui n'est pas une des six valeurs, ou un `verify_integrity` qui n'est pas un booléen. `detail` est une liste, chaque entrée portant `loc`, `type` et `msg`. | Lisez `loc` pour savoir quel paramètre est en cause, puis corrigez sa valeur. |
| 429 | Plus de 60 appels ont été faits depuis votre adresse IP vers une adresse `/passport` dans la fenêtre de 60 secondes en cours. `detail` vaut `Rate limit exceeded: 60 requests per 60s`. | Attendez le nombre de secondes indiqué par l'en-tête `Retry-After`, puis réessayez. |
| 500 | Une erreur inattendue s'est produite pendant le traitement de votre appel. `detail` vaut `Internal Server Error`. | Réessayez. Si l'erreur persiste, contactez le support en indiquant l'heure de l'appel et la valeur de l'en-tête `X-Request-Id` de la réponse. |

> [!INFO] L'ordre des contrôles décide du code renvoyé
> Le serveur contrôle le niveau `authority` en premier, avant même de chercher
> le produit. Un identifiant inconnu demandé avec `access_tier=authority` et
> sans session reçoit donc un 401. Le serveur contrôle les trois niveaux
> de métier après avoir cherché le produit et son passeport. Le même
> identifiant inconnu demandé avec `access_tier=repairer` reçoit donc un 404.

## 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}/verify`](/reference/get-passport-verify/),
  contrôler l'intégrité du passeport publié d'un article.
- [`GET /passport/{identifier}/proof`](/reference/get-passport-proof/),
  rassembler les preuves publiques du passeport d'un article.
- [`GET /passport/{identifier}/vc`](/reference/get-passport-vc/),
  récupérer le justificatif signé du passeport, au format SD-JWT-VC.
- [Publier un passeport numérique de produit](/passeport-dpp/),
  publier, choisir qui voit quels champs, exporter et faire vérifier.
