# GET /passport/{identifier}/vc

Récupérer le justificatif signé d'un passeport, au format SD-JWT-VC, filtré au niveau d'accès demandé. Point d'entrée public aux niveaux public et end_user.

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

---

Vous récupérez le passeport d'un produit sous la forme d'un justificatif
numérique signé par la marque. En quittant cette page, vous saurez demander ce
justificatif au niveau d'accès qui vous concerne, lire les six champs de la
réponse, et savoir où trouver la clef publique qui permet d'en vérifier la
signature sans nous faire confiance.

Adresse complète :

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

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

La réponse est un objet JSON de six champs. Le passeport lui-même y tient dans
un seul champ, sous la forme d'une chaîne de caractères signée au format
SD-JWT-VC. Pour lire les données du passeport sous forme de JSON directement
exploitable, appelez `GET /v1/passport/{identifier}`.

> [!INFO] Ce que veut dire SD-JWT-VC
> C'est un format de justificatif vérifiable à divulgation sélective. La marque
> signe le document une seule fois, à l'émission, avec la totalité des champs.
> Chaque champ non public y occupe un segment séparé, appelé divulgation. Le
> serveur choisit ensuite quels segments il joint à la réponse. Les champs qui
> ne vous concernent pas restent hors de la réponse, et la signature du
> document reste valable.

## Autorisation

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

Une clef d'API partenaire n'ouvre rien ici. Les niveaux qui demandent une
identité s'ouvrent avec un jeton de session de compte utilisateur, jamais avec
une clef d'API.

Quatre valeurs du paramètre `access_tier` exigent une session de compte, que vous
présentez 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 active de réparateur sur la marque du produit, ou un accès à cette marque, ou le rôle d'autorité |
| `recycler` | une session, et une accréditation active de recycleur sur la marque du produit, ou un accès à cette marque, ou le rôle d'autorité |
| `upstream` | une session ayant accès à la marque du produit, ou le rôle d'autorité. Aucune accréditation n'ouvre ce niveau. |
| `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 les produits de cette marque.

Les règles de niveau d'accès sont les mêmes que celles du point d'entrée
`GET /v1/passport/{identifier}`. Ce chemin ne donne donc jamais accès à plus de
champs que la lecture en JSON.

Une différence sépare les deux chemins. Ce point d'entrée ne sert que les
passeports dont la visibilité est publique. Un passeport réservé au
propriétaire ou réservé à la marque n'y est jamais rendu, même à son
propriétaire.

### Le contrôle d'origine

Vos appels de serveur à serveur passent tels quels. Deux situations donnent un
403.

Un appel émis par une page web ouverte sur un domaine qui n'est pas le nôtre
porte un en-tête `Origin` ou `Referer` que nous refusons. N'appelez donc pas ce
point d'entrée depuis le navigateur d'un visiteur, appelez-le depuis votre
serveur.

Un appel qui porte le cookie de session sans en-tête `Origin` ni `Referer` est
refusé lui aussi. Le cookie de session ne vaut que depuis une page servie par
un de nos domaines. Depuis un serveur ou depuis la ligne de commande, présentez
le jeton dans `Authorization: Bearer`.

## Plafond d'appels

60 appels par tranche de 60 secondes, comptés par adresse réseau appelante. La
fenêtre est fixe.

Ce compteur est commun à tous les chemins qui commencent par `/passport`. Les
appels que vous adressez à l'un d'eux entament le budget des autres. Le préfixe
`/v1` ne crée pas un second budget : `/v1/passport/EXEMP1E00001/vc` et
`/passport/EXEMP1E00001/vc` 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 depuis le 1er janvier 1970 |

Un dépassement 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 | Le produit dont vous voulez le justificatif. 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. |

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

### 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 d'identifiant | `0x` suivi de 64 caractères hexadécimaux | Rendue par nos réponses dans le champ `uid_hash` |

L'empreinte d'identifiant existe pour un produit en QR seul comme pour un
produit à puce NFC. Le serveur la tire au hasard pour un produit en QR, il la
dérive de l'identifiant de la puce pour un produit NFC.

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 d'identifiant. Il
cherche toute autre valeur d'abord comme un identifiant de jeton. Il essaie le
numéro de série en dernier, quand les deux premières recherches n'ont rien
donné.

Le serveur reconnaît l'empreinte d'identifiant quelle que soit la casse. Il
reconnaît aussi le numéro de série quelle que soit la casse, et il le
canonicalise comme le fait le résolveur du QR : il lit les lettres `I` et `L`
comme un `1`, la lettre `O` comme un `0`. Vous pouvez donc lui envoyer un
numéro recopié à la main depuis une étiquette.

Ce point d'entrée ne résout que les produits encore au catalogue de la marque.
Un exemplaire détruit sur la chaîne, remplacé par une version ultérieure ou
retiré sans remplacement répond 404.

### 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 que le justificatif révèle |
| --- | --- |
| `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. Ces champs figurent en clair dans le jeton signé, aucune divulgation n'est jointe. |
| `end_user` | impact environnemental, circularité complète, matière principale, matière certifiée biologique, 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 champs du document |

Une marque peut remplacer ces règles par les siennes. Le tableau ci-dessus
décrit ce qui s'applique à défaut de règles propres à la marque. Les mêmes
règles s'appliquent ici et sur la lecture en JSON.

Le serveur refuse toute autre valeur en 422. Il renvoie le niveau réellement
servi dans le champ `access_tier` de la réponse et dans l'en-tête
`X-DPP-Access-Tier`. Lisez l'un des deux plutôt que de le supposer.

## Corps de la requête

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

## Requête d'exemple

Justificatif public du produit dont le numéro imprimé est `EXEMP1E00001`.

> [!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
> TypeScript ci-dessous utilise `fetch`, disponible sans dépendance. Exécutez-le
> côté serveur, sous Node. Depuis un navigateur, le contrôle d'origine décrit
> plus haut refuse l'appel en 403.

:::onglets
```bash title="curl"
curl -s "https://api.sealtrust.io/v1/passport/EXEMP1E00001/vc?access_tier=public"
```
```typescript
const identifiant = "EXEMP1E00001";

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

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

if (response.status === 403) {
  throw new Error(
    "Appel refusé : exécutez cette requête depuis votre serveur, jamais depuis un navigateur.",
  );
}
if (!response.ok) {
  throw new Error(`SealTrust a répondu ${response.status}`);
}

const justificatif = await response.json();

console.log(justificatif.issuer, justificatif.access_tier);
console.log(justificatif.format, justificatif.vct);

const segments = justificatif.sd_jwt_vc.split("~");
console.log("Jeton signé :", segments[0]);
console.log("Segments de divulgation :", segments.slice(1, -1).length);
```
```python
import requests
from urllib.parse import quote

identifiant = "EXEMP1E00001"

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

if response.status_code == 403:
    raise SystemExit(
        "Appel refusé : exécutez cette requête depuis votre serveur, jamais depuis un navigateur."
    )
if not response.ok:
    raise SystemExit(f"SealTrust a répondu {response.status_code}")

justificatif = response.json()

print(justificatif["issuer"], justificatif["access_tier"])
print(justificatif["format"], justificatif["vct"])

segments = justificatif["sd_jwt_vc"].split("~")
print("Jeton signé :", segments[0])
print("Segments de divulgation :", len(segments[1:-1]))
```
:::

Les trois onglets appellent la même adresse avec les mêmes valeurs. L'onglet
`curl` écrit la réponse brute sur la sortie standard. Les onglets TypeScript et
Python en extraient les mêmes champs, dans le même ordre.

## Réponse d'exemple

Code HTTP `200`.

Les valeurs ci-dessous sont fictives. Le jeton signé et les segments de
divulgation sont raccourcis, un jeton réel fait plusieurs milliers de
caractères.

```json
{
  "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": "eyJhbGciOiJFUzI1NiIsInR5cCI6ImRjK3NkLWp3dCIsImtpZCI6ImRpZDp3ZWI6YXBpLnNlYWx0cnVzdC5pbzpicmFuZDo0MjQyI2tleS0xIn0.RVhFTVBMRV9DSEFSR0VfVVRJTEU.RVhFTVBMRV9TSUdOQVRVUkU~"
}
```

| Champ | Type | Présence | Description |
| --- | --- | --- | --- |
| `passport_id` | `integer` | toujours | L'identifiant de la version du passeport à laquelle se rapporte ce justificatif. |
| `issuer` | `string` | toujours | L'identifiant décentralisé `did:web` de la marque qui a signé. C'est lui qui mène à la clef publique de vérification. Toujours renseigné sur ce point d'entrée. |
| `vct` | `string` | toujours | L'identifiant du modèle de justificatif. Vaut `https://schema.sealtrust.io/vct/digital-product-passport` à défaut de valeur enregistrée sur le passeport. |
| `access_tier` | `string` | toujours | Le niveau réellement servi. |
| `format` | `string` | toujours | Toujours `dc+sd-jwt`. C'est le type de média du justificatif à divulgation sélective. |
| `sd_jwt_vc` | `string` | toujours | Le justificatif lui-même. Voir ci-dessous. |

### 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é. |

### Lire le champ `sd_jwt_vc`

Le contenu de `sd_jwt_vc` est une suite de segments séparés par le caractère
`~`. Le dernier segment est toujours vide, donc la chaîne se termine par un `~`.

```text
<jeton signé>~<divulgation>~<divulgation>~
```

Le premier segment est un jeton signé en trois parties, séparées par des points.
L'en-tête et la charge utile sont encodés en base64url, sans remplissage. Vous
les décodez sans clef.

L'en-tête porte trois valeurs.

| Valeur | Contenu |
| --- | --- |
| `alg` | `ES256`. La signature est une signature ECDSA sur la courbe P-256. |
| `typ` | `dc+sd-jwt` |
| `kid` | L'identifiant de la clef qui a signé, sous la forme `<did de la marque>#key-<numéro de version>`. |

La charge utile porte les champs suivants.

| Champ | Contenu |
| --- | --- |
| `iss` | L'identifiant `did:web` de la marque émettrice. Il vaut la même valeur que le champ `issuer` de la réponse. |
| `vct` | L'identifiant du modèle de justificatif. |
| `iat` | La date d'émission, en secondes depuis le 1er janvier 1970. |
| `@context` | `["https://www.w3.org/ns/credentials/v2", "https://schema.sealtrust.io/dpp/v1"]` |
| `type` | `["VerifiableCredential", "DigitalProductPassport"]` |
| `issuer` | Répétition de `iss`, attendue par le modèle de données des justificatifs vérifiables. |
| `validFrom` | La date d'émission au format ISO 8601, à la seconde, en temps universel. |
| `credentialSubject` | Les données du passeport. Les champs publics y figurent en clair. Les autres sont remplacés par des empreintes, sous la clef `_sd`. |
| `credentialSchema` | Un objet à deux clefs, `id` qui reprend `vct`, et `type` qui vaut `JsonSchema`. |
| `product` | L'identité du produit : `uid_hash`, `token_id` et `name`. Jamais masquée. |
| `brand` | L'identité de la marque : `name`, `lei_code`, `eori_number`, `website_url`, `postal_address`, `contact_email`. Jamais masquée. Les valeurs non renseignées sont absentes. |

Un passeport peut porter sur un modèle de produit ou sur un exemplaire précis.
Quand la marque publie un passeport de modèle, il vaut pour tous les
exemplaires qui partagent le même code produit, et le justificatif émis à cette
publication porte `uid_hash` et `token_id` à `null` dans le bloc `product` :
il ne désigne aucun exemplaire en particulier.

Les segments suivants sont les divulgations. Chacun est un tableau de trois
éléments encodé en base64url : un sel, le nom du champ, sa valeur. Vous les
décodez sans clef. Leur nombre dépend du niveau demandé.

```text
WyJFWEVNUExFMDAwMDAwMDAwMDAwMDAwMCIsInJlcGFpcmFiaWxpdHlfaW5kZXgiLDguMl0
```

Ce segment d'exemple se décode en
`["EXEMPLE0000000000000000", "repairability_index", 8.2]`.

Au niveau `public`, la réponse ne révèle rien de plus que les champs toujours
présents dans le document signé. La chaîne se réduit alors au jeton signé suivi
d'un `~`. Chaque autre niveau ajoute les divulgations qui le concernent.

> [!ATTENTION] Le document est signé une fois, la réponse est filtrée à chaque appel
> La signature couvre le jeton, elle ne couvre pas la liste des divulgations
> jointes. Une réponse à laquelle il manque des segments reste vérifiable. Ne
> concluez pas d'un champ absent qu'il n'existe pas dans le passeport, concluez
> qu'il ne vous est pas ouvert à ce niveau.

### Vérifier la signature vous-même

Le champ `issuer` porte un identifiant `did:web`. Il désigne un document
public qui contient les clefs publiques de la marque, exprimées en
`JsonWebKey2020`. La clef à utiliser est celle dont l'identifiant correspond au
`kid` de l'en-tête du jeton. Ce document ne contient que les clefs non
révoquées, donc une clef révoquée n'y figure plus.

Un identifiant de la forme `did:web:api.sealtrust.io:brand:4242` se résout à
`https://api.sealtrust.io/brand/4242/did.json`. Un identifiant de la forme
`did:web:id.exemple-sas.example` se résout à
`https://id.exemple-sas.example/.well-known/did.json`. Une marque peut héberger
elle-même ce document sur son propre domaine, auquel cas la vérification de ses
passeports ne dépend d'aucun de nos serveurs.

Si vous préférez que la vérification soit faite pour vous, appelez
`GET /v1/passport/{identifier}/vc/verify`.

## Erreurs

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

| Code | Condition | Que faire |
| --- | --- | --- |
| 401 | Vous demandez `authority` sans session. `detail` vaut `Authority-tier access requires authentication`. | Connectez-vous, puis présentez le jeton de session dans `Authorization: Bearer`. |
| 401 | Vous demandez `repairer`, `recycler` ou `upstream` sans session. `detail` vaut `Professional-tier access requires authentication`. | Connectez-vous, puis présentez le jeton de session dans `Authorization: Bearer`. Une clef d'API ne convient pas. |
| 403 | Vous demandez `authority` avec une session qui ne porte pas ce rôle. `detail` vaut `Authority-tier access is restricted to market surveillance authorities`. | Demandez un niveau qui correspond à votre situation. |
| 403 | Vous demandez un niveau professionnel sans accréditation active sur la marque du produit, sans accès à cette marque et sans le rôle d'autorité. `detail` commence par `This tier is restricted to the product's brand`. | Demandez à la marque de vous accréditer, puis redemandez le niveau qui correspond à votre métier. |
| 403 | L'appel porte un en-tête `Origin` ou `Referer` qui ne désigne pas un de nos domaines, ce qui arrive pour tout appel émis depuis une page web hébergée ailleurs. `detail` vaut `Forbidden origin`. | Appelez ce point d'entrée depuis votre serveur, jamais depuis le navigateur d'un visiteur. |
| 403 | L'appel porte un cookie de session sans en-tête `Origin` ni `Referer`. `detail` vaut `Origin or Referer header required`. | Présentez le jeton dans `Authorization: Bearer` au lieu du cookie de session. |
| 404 | Aucun produit au catalogue ne correspond à cet identifiant, sous aucune des trois formes acceptées. `detail` vaut `Product not found`. | Vérifiez l'identifiant. Un exemplaire détruit sur la chaîne, remplacé par une version ultérieure ou retiré donne cette même réponse. |
| 404 | Le produit existe, mais aucun passeport publié en visibilité publique ne lui est rattaché, ni directement, ni par son modèle. `detail` vaut `No published passport found for this product`. | Ne traitez pas cette réponse comme un échec. Ce produit n'a pas de passeport public. Un passeport réservé au propriétaire ou à la marque donne la même réponse. |
| 404 | Le passeport existe et il est public, mais aucun justificatif signé n'a encore été émis pour lui. `detail` commence par `No VC issued for this passport yet`. | Lisez le passeport en JSON avec `GET /v1/passport/{identifier}`. Publier une version d'un passeport émet son justificatif : demandez à la marque de republier la version en cours. |
| 404 | La marque du passeport n'est pas résolvable. `detail` vaut `Brand not found`. | Signalez-le au support. Aucune action de votre côté ne change cette réponse. |
| 422 | La valeur de `access_tier` ne fait pas partie des six acceptées. `detail` est une liste d'objets qui nomment le paramètre en cause. | Corrigez la valeur. Les six valeurs acceptées sont listées plus haut. |
| 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. 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/{identifier}/vc/preview`](/reference/get-passport-vc-preview/),
  voir, sans signature, ce qu'un niveau d'accès exposerait.
- [`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.
