# GET /passport/{identifier}/vc/preview

Voir, sans signature et sans rien enregistrer, l'enveloppe de justificatif vérifiable et le document JSON-LD qu'un niveau d'accès donné exposerait pour un passeport.

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

---

Ce point d'entrée rend, sans signature et sans rien enregistrer, l'enveloppe de
justificatif vérifiable et le document JSON-LD qu'un niveau d'accès donné
exposerait pour le passeport d'un produit.

## Autorisation

Aucune pour les niveaux `public` et `end_user`. Ce point d'entrée est alors
ouvert, sans clef d'API ni session.

Les quatre autres valeurs du paramètre `access_tier` exigent une session de
compte. Depuis votre serveur, présentez-la dans l'en-tête
`Authorization: Bearer <jeton de session>`. Le cookie `access_token` ouvre les
mêmes niveaux, uniquement dans un appel qui porte aussi un en-tête `Origin` ou
`Referer` que nous acceptons, donc depuis nos propres pages. Une clef d'API
partenaire ne convient pas : ce point d'entrée ne lit qu'un jeton de session,
dans l'en-tête `Authorization` ou dans le cookie. Une clef d'API n'ouvre donc
aucun niveau au-delà de `public` et de `end_user`.

| Niveau demandé | Ce qu'il faut présenter |
| --- | --- |
| `public` | rien |
| `end_user` | rien |
| `repairer` | une session dont le compte porte une accréditation de réparateur active sur la marque du produit, une session de la marque elle-même, ou une session portant le rôle d'autorité de surveillance du marché |
| `recycler` | une session dont le compte porte une accréditation de recycleur active sur la marque du produit, une session de la marque elle-même, ou une session portant le rôle d'autorité de surveillance du marché |
| `upstream` | une session de la marque du produit, ou une session portant le rôle d'autorité de surveillance du marché |
| `authority` | une session portant le rôle d'autorité de surveillance du marché |

> [!INFO] Une accréditation n'en ouvre aucune autre
> Les trois niveaux de métier sont des publics distincts, et aucun ne contient
> les autres. Un recycleur accrédité atteint la composition matière et
> les substances préoccupantes ; il n'atteint ni la nomenclature du réparateur,
> ni la fabrication et la chaîne d'approvisionnement du fournisseur amont.
> Accréditer un partenaire dans un métier ne lui ouvre donc que ce métier.

Nous ne servons ici que les passeports en visibilité publique. Un passeport
réservé au propriétaire du produit répond 404 sur ce point d'entrée, y compris
pour ce propriétaire, alors que `GET /v1/passport/{identifier}` le lui sert. Un
passeport réservé à la marque n'est jamais rendu ici.

### Contrôle de l'origine

Ce point d'entrée refuse tout appel dont l'en-tête `Origin` ou `Referer`
désigne un domaine qui n'est pas le nôtre, avec 403 `Forbidden origin`. Le
refus ne regarde pas la nature du client : un programme lancé sur votre serveur
qui envoie un `Referer` reçoit le même 403 qu'une page web.

Deux règles pour appeler depuis votre serveur.

- N'envoyez pas d'en-tête `Referer`. La plupart des bibliothèques HTTP n'en
  envoient aucun tant que vous ne le demandez pas.
- Présentez votre session dans `Authorization: Bearer <jeton de session>`. Un
  appel de serveur qui s'appuie sur le cookie `access_token` est refusé avec
  403 `Origin or Referer header required`.

N'appelez pas cette adresse depuis le navigateur de votre visiteur : un appel
JavaScript lancé depuis une page hébergée ailleurs que chez nous est refusé.

## 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/1042/vc/preview` et
`/passport/1042/vc/preview` 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 |
| --- | --- | --- | --- |
| `identifier` | `string` | oui | L'article dont vous voulez l'aperçu. 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. |

`identifier` accepte trois formes, essayées dans cet ordre.

| Forme | Aspect | Provenance |
| --- | --- | --- |
| Empreinte d'identifiant | `0x` suivi de 64 caractères hexadécimaux | l'empreinte de l'identifiant unique de l'article. Nous la lisons sur la puce pour un article NFC, et nous la tirons au hasard à la frappe pour un article QR |
| Identifiant de jeton | un nombre écrit en décimal | l'identifiant de l'article sur la chaîne |
| Numéro de série imprimé | 12 caractères | ce que porte le QR code sur le produit, dans l'adresse `/p/{serial}` |

Vous pouvez écrire le numéro de série en minuscules ou en majuscules. Nous
ramenons les caractères qui se ressemblent à une forme unique avant la
recherche, donc un `I` ou un `L` saisi à la main retrouve le `1`, et un `O`
retrouve le `0`.

Ce point d'entrée ne résout que les articles encore au catalogue de la marque.
Un article détruit sur la chaîne, remplacé par une version ultérieure ou
archivé répond 404. `GET /v1/passport/{identifier}` se comporte autrement : il
continue de servir le dernier passeport publié pour ces articles.

### Les six valeurs de `access_tier`

Ces niveaux ne forment pas une échelle. Ils décrivent six publics dont les
besoins diffèrent. Un recycleur et un réparateur voient des données
différentes.

| Valeur | Sections du passeport retenues avant rendu |
| --- | --- |
| `public` | identité du produit, conformité ESPR, conformité REACH, marquage CE, taux de recyclabilité, taux de matière recyclée, étiquettes, spécification de batterie |
| `end_user` | tout le niveau `public`, plus impact environnemental, circularité complète, matière principale, mention de coton biologique certifié, durabilité, efficacité énergétique, empreinte carbone |
| `repairer` | tout le niveau `end_user`, plus nomenclature, lien vers les instructions de démontage, indice de réparabilité, état de santé de la batterie |
| `recycler` | tout le niveau `end_user`, plus composition matière complète, substances préoccupantes, lien vers les instructions de démontage, état de santé de la batterie |
| `upstream` | tout le niveau `end_user`, plus composition matière complète, substances préoccupantes, fabrication, chaîne d'approvisionnement |
| `authority` | l'intégralité des données, sans filtrage |

Ce tableau décrit le filtre appliqué avant la conversion en JSON-LD.
Plusieurs de ces sections restent absentes du document rendu, parce que ce
point d'entrée ne les convertit pas. L'encadré plus bas les liste toutes.

Une marque peut resserrer ou élargir ces listes pour ses propres produits. Les
valeurs ci-dessus sont celles qui s'appliquent quand elle n'a rien changé.

### L'adresse complète et l'alias sans `/v1`

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

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

### Ce que cet aperçu ne prouve pas

Un justificatif vérifiable est un document que son émetteur signe, et que
n'importe qui peut contrôler ensuite sans nous redemander quoi que ce soit. Ce
point d'entrée en montre la forme avant signature.

> [!ATTENTION] Cet aperçu n'est pas signé et ne prouve rien
> Le champ `signed` vaut toujours `false`. Aucune signature n'est calculée,
> rien n'est enregistré, et le contenu rendu ici n'engage personne. Pour un
> document signé et opposable, appelez `GET /v1/passport/{identifier}/vc`.
> Servez-vous de cet aperçu pour préparer votre intégration et pour vérifier
> ce que chaque niveau d'accès laisse voir.

## Corps de la requête

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

## Requête d'exemple

Aperçu public du justificatif de l'article dont le numéro de série imprimé est
`EXEMPLE00001`.

> [!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/EXEMPLE00001/vc/preview?access_tier=public"
```
```typescript
const url = new URL(
  "https://api.sealtrust.io/v1/passport/EXEMPLE00001/vc/preview",
);
url.searchParams.set("access_tier", "public");

const reponse = await fetch(url);

if (reponse.status === 404) {
  console.log("Aucun passeport public pour cet identifiant.");
} else if (reponse.ok) {
  const apercu = await reponse.json();

  console.log("Émetteur :", apercu.issuer);
  console.log("Modèle de justificatif :", apercu.vct);
  console.log("Niveau demandé :", apercu.access_tier);
  console.log("Signé :", apercu.signed);

  const sujet = apercu.credentialSubject;
  console.log(sujet.name, sujet.gtin, sujet.brand.name);

  for (const propriete of sujet.additionalProperty ?? []) {
    console.log(propriete.name, propriete.value, propriete.unitText ?? "");
  }
} else {
  console.log(reponse.status, await reponse.json());
}
```
```python
import requests

response = requests.get(
    "https://api.sealtrust.io/v1/passport/EXEMPLE00001/vc/preview",
    params={"access_tier": "public"},
    timeout=30,
)

if response.status_code == 404:
    print("Aucun passeport public pour cet identifiant.")
elif response.ok:
    apercu = response.json()

    print("Émetteur :", apercu["issuer"])
    print("Modèle de justificatif :", apercu["vct"])
    print("Niveau demandé :", apercu["access_tier"])
    print("Signé :", apercu["signed"])

    sujet = apercu["credentialSubject"]
    print(sujet.get("name"), sujet.get("gtin"), sujet["brand"]["name"])

    for propriete in sujet.get("additionalProperty", []):
        print(propriete["name"], propriete["value"], propriete.get("unitText", ""))
else:
    print(response.status_code, response.json())
```
:::

## Réponse d'exemple

Code HTTP `200`.

```json
{
  "@context": [
    "https://www.w3.org/ns/credentials/v2",
    "https://schema.sealtrust.io/dpp/v1"
  ],
  "type": ["VerifiableCredential", "DigitalProductPassport"],
  "issuer": "did:web:api.sealtrust.io:brand:4242",
  "vct": "https://schema.sealtrust.io/vct/digital-product-passport",
  "credentialSubject": {
    "@context": {
      "@vocab": "https://schema.org/",
      "gs1": "https://gs1.org/voc/",
      "espr": "https://data.europa.eu/espr/"
    },
    "@type": "Product",
    "identifier": "0x1111111111111111111111111111111111111111111111111111111111111111",
    "gtin": "03701234567890",
    "name": "Modèle Exemple 001",
    "brand": {
      "@type": "Brand",
      "name": "Exemple SAS",
      "identifier": "00000000000000000000",
      "url": "https://exemple.example"
    },
    "countryOfOrigin": "FR",
    "material": [],
    "additionalProperty": [
      {
        "@type": "PropertyValue",
        "name": "Recyclability (EN 45555)",
        "value": 82,
        "unitText": "percent"
      },
      {
        "@type": "PropertyValue",
        "name": "gs1:recycledContentPercentage",
        "value": 35,
        "unitText": "percent"
      }
    ],
    "espr:compliance": {
      "@type": "espr:ComplianceDeclaration",
      "espr:euEsprCompliant": true,
      "espr:reachCompliant": true,
      "espr:ceMarking": true
    }
  },
  "access_tier": "public",
  "signed": false,
  "note": "Unsigned preview — POST /vc/issue to mint the signed SD-JWT-VC."
}
```

| Champ | Type | Description |
| --- | --- | --- |
| `@context` | `string[]` | Les deux vocabulaires du document, dans cet ordre : le modèle de justificatif vérifiable du W3C, puis le nôtre. |
| `type` | `string[]` | Toujours `["VerifiableCredential", "DigitalProductPassport"]`. |
| `issuer` | `string` | L'identifiant `did:web` de la marque qui émettrait ce justificatif. Voir ci-dessous. |
| `vct` | `string` | L'identifiant du modèle de justificatif. Vaut `https://schema.sealtrust.io/vct/digital-product-passport` quand la marque n'en a pas défini un autre. |
| `credentialSubject` | `object` | Le passeport rendu en JSON-LD, filtré au niveau demandé. Voir ci-dessous. |
| `access_tier` | `string` | Le niveau que vous avez demandé. |
| `signed` | `boolean` | Toujours `false` sur ce point d'entrée. |
| `note` | `string` | Un texte fixe, en anglais, qui rappelle que l'aperçu n'est pas signé. Ne branchez aucun code dessus. |

Le champ `access_tier` de la réponse et l'en-tête `X-DPP-Access-Tier`
reprennent le niveau que vous avez demandé. L'appel réussit au niveau demandé
ou échoue en 401 ou en 403. Il n'y a pas de repli silencieux vers un niveau
plus bas.

La réponse porte `Cache-Control: no-store, max-age=0`. Aucun cache partagé ne
doit donc conserver une réponse obtenue à un niveau professionnel.

### Le champ `issuer`

C'est l'identité de l'émetteur, sous la forme `did:web`. Elle prend deux
formes selon ce que la marque a choisi.

| Forme | Où se lit le document d'identité |
| --- | --- |
| `did:web:<hôte>:brand:<numéro>` | `https://<hôte>/brand/<numéro>/did.json` |
| `did:web:<domaine de la marque>` | `https://<domaine de la marque>/.well-known/did.json` |

La première forme s'applique par défaut, et la marque n'a rien à faire pour
l'obtenir. La seconde demande que la marque déclare son propre domaine et y
publie son document d'identité.

Lisez la valeur rendue telle quelle. Ne la reconstruisez pas de votre côté :
une marque peut passer d'une forme à l'autre.

### Le champ `credentialSubject`

C'est le passeport rendu en JSON-LD, avec le vocabulaire Schema.org, le
vocabulaire web de GS1 et nos extensions ESPR. Il porte son propre `@context`,
qui est un objet, alors que celui du premier niveau est une liste. Les deux
coexistent normalement.

| Champ | Type | Présence | Description |
| --- | --- | --- | --- |
| `@context` | `object` | toujours | Les trois vocabulaires employés dans ce document. |
| `@type` | `string` | toujours | Toujours `Product`. |
| `identifier` | `string` | toujours pour un article frappé | L'empreinte de l'identifiant unique de l'article. Elle existe aussi bien pour un article QR seul que pour un article à puce. |
| `gtin` | `string` | si renseigné | Le code article GS1 du produit. |
| `name` | `string` | si renseigné | Le modèle déclaré dans le passeport. À défaut, le nom du produit. |
| `brand` | `object` | toujours | La marque : `name`, et selon ce qu'elle a renseigné `identifier` (son code LEI), `url`, `address`, `email`. |
| `countryOfOrigin` | `string` | si renseigné | Le pays de fabrication déclaré. |
| `gs1:productionFacility` | `string` | si renseigné | Le site de production déclaré. |
| `espr:operatorIdentifier` | `string` | si renseigné | L'identifiant de l'opérateur économique au sens de l'ESPR. |
| `espr:batteryPassportIdentifier` | `string` | si renseigné | L'identifiant de passeport de batterie. |
| `espr:uniqueBatteryIdentifier` | `string` | si renseigné | L'identifiant unique de la batterie. |
| `espr:eprelRegistration` | `string` | si renseigné et visible | Le numéro d'enregistrement EPREL de l'étiquette énergie. |
| `material` | `object[]` | toujours | La composition matière. Liste vide quand aucune matière n'est visible au niveau demandé. |
| `additionalProperty` | `object[]` | toujours | Les mesures environnementales, de circularité, de batterie et d'efficacité énergétique, sous forme de couples nom et valeur. Liste vide quand aucune n'est visible. |
| `maintenanceTechnicalDataUrl` | `string` | si renseigné et visible | Le lien vers les instructions de démontage. |
| `espr:compliance` | `object` | si la section conformité est visible | Les déclarations de conformité retenues au niveau demandé. |

Une entrée de `additionalProperty` porte `@type` valant `PropertyValue`, un
`name` en anglais, une `value`, et un `unitText` quand la grandeur a une unité.
Les noms sont ceux du vocabulaire, par exemple `Recyclability (EN 45555)` ou
`gs1:recycledContentPercentage`. Branchez votre code sur `name`, sur la valeur
exacte, sans traduction.

> [!ATTENTION] Toutes les données du passeport ne sont pas rendues ici
> Le rendu JSON-LD couvre l'identité du produit, l'impact environnemental, la
> circularité, les matières, la conformité, l'efficacité énergétique, la
> spécification de batterie et l'état de santé de la batterie. Les autres
> sections du passeport ne sont pas converties par ce point d'entrée, même à un
> niveau qui les autorise. C'est le cas des étiquettes, de la section empreinte
> carbone de premier niveau, des substances préoccupantes, de la nomenclature,
> de la fabrication, de la chaîne d'approvisionnement, de la durabilité et de
> la garantie. Un aperçu au niveau `recycler` ou `upstream` ne les fera donc
> pas apparaître. Pour ces données, lisez `GET /v1/passport/{identifier}` au
> niveau qui vous est ouvert.

## 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. Un jeton expiré est traité comme une absence de session. Sinon, demandez le niveau `public` ou `end_user`. |
| 403 | L'appel porte un en-tête `Origin` ou `Referer` qui désigne un domaine qui n'est pas le nôtre. `detail` vaut `Forbidden origin`. | Depuis votre serveur, cessez d'envoyer un en-tête `Referer`, ou présentez votre session dans `Authorization: Bearer <jeton>`. |
| 403 | L'appel n'envoie ni `Origin` ni `Referer`, et porte un cookie `access_token`. `detail` vaut `Origin or Referer header required`. | Depuis votre serveur, présentez la session dans `Authorization: Bearer <jeton>` au lieu du cookie. |
| 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 | `access_tier` vaut `repairer`, `recycler` ou `upstream`, et le compte connecté n'appartient ni à la marque du produit, ni aux autorités, et ne porte pas 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`. | Faites-vous accréditer par la marque du produit, puis demandez le niveau de votre métier. |
| 404 | Aucun passeport public ne répond à cet identifiant. Soit aucun article au catalogue ne correspond à cet identifiant, soit l'article existe et ne porte aucun passeport publié en visibilité publique, ni directement, ni par son modèle. | Ne traitez pas cette réponse comme une panne. Vérifiez votre identifiant, et prévoyez le cas d'un article sans passeport public. Un passeport réservé au propriétaire ou à la marque donne la même réponse, comme un article détruit sur la chaîne, remplacé ou archivé. |
| 404 | Le passeport trouvé renvoie à une marque qui n'existe plus. `detail` vaut `Brand not found`. | Signalez le cas au support. Aucune action de votre côté ne corrige cet état. |
| 422 | La valeur de `access_tier` n'est pas une des six valeurs acceptées. `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 | Le plafond de 60 appels par 60 secondes est atteint pour votre adresse réseau, sur l'ensemble des chemins `/passport`. `detail` vaut `Rate limit exceeded: 60 requests per 60s`. | Attendez le nombre de secondes indiqué par `Retry-After`, puis réessayez. Espacez vos appels. |
| 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`. |

Le code porte encore un dernier cas, 422 avec `detail` valant
`Brand has no website_url; cannot derive a did:web issuer`. Vous ne le
rencontrerez pas : toute marque enregistrée reçoit une identité d'émetteur,
`did:web:api.sealtrust.io:brand:<numéro>` quand elle n'a pas déclaré son propre
domaine.

## Voir aussi

- [`GET /passport/{identifier}/vc`](/reference/get-passport-vc/),
  récupérer le justificatif signé du passeport, au format SD-JWT-VC.
- [`GET /passport/{identifier}/vc/verify`](/reference/get-passport-vc-verify/),
  contrôler la signature du justificatif et lire les données révélées.
- [`GET /brand/{brand_id}/did.json`](/reference/get-brand-did-json/),
  récupérer les clefs publiques de signature d'une marque.
- [Publier un passeport numérique de produit](/passeport-dpp/),
  publier, choisir qui voit quels champs, exporter et faire vérifier.
