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

See, without a signature and without recording anything, the verifiable credential envelope and the JSON-LD document that a given access tier would expose for a passport.

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

---

This endpoint returns, without a signature and without recording anything, the
verifiable credential envelope and the JSON-LD document that a given access tier
would expose for a product's passport.

## Authorization

None for the `public` and `end_user` tiers. This endpoint is then open, with no
API key and no session.

The four other values of the `access_tier` parameter require an account session.
From your server, present it in the header
`Authorization: Bearer <session token>`. The `access_token` cookie opens the
same tiers, only in a call that also carries an `Origin` or `Referer` header
that we accept, that is, from our own pages. A partner API key does not work:
this endpoint reads only a session token, in the `Authorization` header or in
the cookie. An API key therefore opens no tier beyond `public` and `end_user`.

| Tier requested | What you must present |
| --- | --- |
| `public` | nothing |
| `end_user` | nothing |
| `repairer` | a session whose account holds an active repairer accreditation on the product's brand, a session of the brand itself, or a session carrying the market surveillance authority role |
| `recycler` | a session whose account holds an active recycler accreditation on the product's brand, a session of the brand itself, or a session carrying the market surveillance authority role |
| `upstream` | a session of the product's brand, or a session carrying the market surveillance authority role |
| `authority` | a session carrying the market surveillance authority role |

> [!INFO] One accreditation opens no other
> The three trade tiers are distinct audiences, and none of them contains the
> others. An accredited recycler reaches the material composition and the
> substances of concern; it reaches neither the repairer's bill of materials,
> nor the manufacturing and supply chain of the upstream supplier. Accrediting a
> partner in one trade therefore opens only that trade to them.

We serve here only the passports with public visibility. A passport reserved for
the product's owner answers 404 on this endpoint, including for that owner,
whereas `GET /v1/passport/{identifier}` serves it to them. A passport reserved
for the brand is never returned here.

### Origin check

This endpoint refuses any call whose `Origin` or `Referer` header designates a
domain that is not ours, with 403 `Forbidden origin`. The refusal does not look
at the nature of the client: a program launched on your server that sends a
`Referer` receives the same 403 as a web page.

Two rules for calling from your server.

- Do not send a `Referer` header. Most HTTP libraries send none until you ask
  for it.
- Present your session in `Authorization: Bearer <session token>`. A server call
  that relies on the `access_token` cookie is refused with 403
  `Origin or Referer header required`.

Do not call this address from your visitor's browser: a JavaScript call launched
from a page hosted anywhere other than with us is refused.

## Rate limit

60 calls per 60 second window, counted per calling network address.

This counter is shared by every path that starts with `/passport`. The calls you
address to one of them therefore eat into the budget of the others. The `/v1`
prefix does not create a second budget: `/v1/passport/1042/vc/preview` and
`/passport/1042/vc/preview` fill the same counter.

Every accepted response carries three headers.

| Header | Contents |
| --- | --- |
| `X-RateLimit-Limit` | the limit applied over the window, here `60` |
| `X-RateLimit-Remaining` | what is left to you in the current window |
| `X-RateLimit-Reset` | the timestamp of the end of the window, in seconds |

A refusal returns 429, with those three headers and `Retry-After`. On this
endpoint, `Retry-After` equals the duration of the window, that is 60 seconds.

## Path and query parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `identifier` | `string` | yes | The item you want the preview of. Three forms are accepted, see below. |
| `access_tier` | `string` | no | The access tier requested. Equals `public` by default. Six accepted values, listed below. |

`identifier` accepts three forms, tried in this order.

| Form | Appearance | Origin |
| --- | --- | --- |
| Identifier hash | `0x` followed by 64 hexadecimal characters | the hash of the item's unique identifier. We read it on the chip for an NFC item, and we draw it at random at minting time for a QR item |
| Token identifier | a number written in decimal | the item's identifier on the chain |
| Printed serial number | 12 characters | what the QR code on the product carries, in the address `/p/{serial}` |

You can write the serial number in lowercase or in uppercase. We reduce the
characters that look alike to a single form before the lookup, so an `I` or an
`L` entered by hand finds the `1`, and an `O` finds the `0`.

This endpoint resolves only the items still in the brand's catalog. An item
destroyed on the chain, replaced by a later version or archived answers 404.
`GET /v1/passport/{identifier}` behaves differently: it keeps serving the last
published passport for those items.

### The six values of `access_tier`

These tiers do not form a scale. They describe six audiences whose needs
differ. A recycler and a repairer see different data.

| Value | Passport sections kept before rendering |
| --- | --- |
| `public` | product identity, ESPR compliance, REACH compliance, CE marking, recyclability rate, recycled material rate, labels, battery specification |
| `end_user` | the whole `public` tier, plus environmental impact, full circularity, primary material, certified organic cotton statement, durability, energy efficiency, carbon footprint |
| `repairer` | the whole `end_user` tier, plus bill of materials, link to the disassembly instructions, repairability index, battery state of health |
| `recycler` | the whole `end_user` tier, plus full material composition, substances of concern, link to the disassembly instructions, battery state of health |
| `upstream` | the whole `end_user` tier, plus full material composition, substances of concern, manufacturing, supply chain |
| `authority` | the entirety of the data, with no filtering |

This table describes the filter applied before the conversion to JSON-LD.
Several of these sections stay absent from the document returned, because this
endpoint does not convert them. The callout further down lists them all.

A brand can tighten or widen these lists for its own products. The values above
are the ones that apply when it has changed nothing.

### The full address and the alias without `/v1`

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

The same endpoint also answers without the `/v1` prefix, at
`https://api.sealtrust.io/passport/{identifier}/vc/preview`. Both addresses call
the same code. Use the `/v1` form for a new integration.

### What this preview does not prove

A verifiable credential is a document that its issuer signs, and that anyone can
check afterwards without asking us for anything again. This endpoint shows its
shape before signing.

> [!ATTENTION] This preview is not signed and proves nothing
> The `signed` field always equals `false`. No signature is computed, nothing is
> recorded, and the content returned here binds no one. For a signed and binding
> document, call `GET /v1/passport/{identifier}/vc`. Use this preview to prepare
> your integration and to check what each access tier lets through.

## Request body

None. This request has no body.

## Example request

Public preview of the credential of the item whose printed serial number is
`EXEMPLE00001`.

> [!INFO] The TypeScript SDK does not cover this endpoint
> No method of the `@sealtrust-io/sdk` package calls this address. The
> TypeScript example below uses `fetch`, with no dependency.

:::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())
```
:::

## Example response

HTTP code `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."
}
```

| Field | Type | Description |
| --- | --- | --- |
| `@context` | `string[]` | The two vocabularies of the document, in this order: the W3C verifiable credential model, then ours. |
| `type` | `string[]` | Always `["VerifiableCredential", "DigitalProductPassport"]`. |
| `issuer` | `string` | The `did:web` identifier of the brand that would issue this credential. See below. |
| `vct` | `string` | The identifier of the credential type. Equals `https://schema.sealtrust.io/vct/digital-product-passport` when the brand has not defined another one. |
| `credentialSubject` | `object` | The passport rendered in JSON-LD, filtered at the tier requested. See below. |
| `access_tier` | `string` | The tier you requested. |
| `signed` | `boolean` | Always `false` on this endpoint. |
| `note` | `string` | A fixed text, in English, that recalls that the preview is not signed. Do not hook any code on it. |

The response's `access_tier` field and the `X-DPP-Access-Tier` header both
repeat the tier you requested. The call succeeds at the tier requested or fails
with 401 or 403. There is no silent fallback to a lower tier.

The response carries `Cache-Control: no-store, max-age=0`. No shared cache must
therefore keep a response obtained at a professional tier.

### The `issuer` field

It is the identity of the issuer, in `did:web` form. It takes two forms
depending on what the brand has chosen.

| Form | Where the identity document is read |
| --- | --- |
| `did:web:<host>:brand:<number>` | `https://<host>/brand/<number>/did.json` |
| `did:web:<brand's domain>` | `https://<brand's domain>/.well-known/did.json` |

The first form applies by default, and the brand has nothing to do to get it.
The second requires the brand to declare its own domain and to publish its
identity document there.

Read the value returned as it is. Do not rebuild it on your side: a brand can
move from one form to the other.

### The `credentialSubject` field

It is the passport rendered in JSON-LD, with the Schema.org vocabulary, the GS1
web vocabulary and our ESPR extensions. It carries its own `@context`, which is
an object, whereas the one at the top level is a list. The two coexist normally.

| Field | Type | Presence | Description |
| --- | --- | --- | --- |
| `@context` | `object` | always | The three vocabularies used in this document. |
| `@type` | `string` | always | Always `Product`. |
| `identifier` | `string` | always for a minted item | The hash of the item's unique identifier. It exists for a QR-only item as well as for a chipped item. |
| `gtin` | `string` | if filled in | The product's GS1 trade item code. |
| `name` | `string` | if filled in | The model declared in the passport. Failing that, the product's name. |
| `brand` | `object` | always | The brand: `name`, and depending on what it has filled in `identifier` (its LEI code), `url`, `address`, `email`. |
| `countryOfOrigin` | `string` | if filled in | The declared country of manufacture. |
| `gs1:productionFacility` | `string` | if filled in | The declared production site. |
| `espr:operatorIdentifier` | `string` | if filled in | The identifier of the economic operator in the sense of the ESPR. |
| `espr:batteryPassportIdentifier` | `string` | if filled in | The battery passport identifier. |
| `espr:uniqueBatteryIdentifier` | `string` | if filled in | The unique battery identifier. |
| `espr:eprelRegistration` | `string` | if filled in and visible | The EPREL registration number of the energy label. |
| `material` | `object[]` | always | The material composition. Empty list when no material is visible at the tier requested. |
| `additionalProperty` | `object[]` | always | The environmental, circularity, battery and energy efficiency measurements, as name and value pairs. Empty list when none is visible. |
| `maintenanceTechnicalDataUrl` | `string` | if filled in and visible | The link to the disassembly instructions. |
| `espr:compliance` | `object` | if the compliance section is visible | The compliance declarations kept at the tier requested. |

An entry of `additionalProperty` carries `@type` equal to `PropertyValue`, a
`name` in English, a `value`, and a `unitText` when the quantity has a unit. The
names are the ones of the vocabulary, for example `Recyclability (EN 45555)` or
`gs1:recycledContentPercentage`. Hook your code on `name`, on the exact value,
with no translation.

> [!ATTENTION] Not all the passport's data is returned here
> The JSON-LD rendering covers product identity, environmental impact,
> circularity, materials, compliance, energy efficiency, battery specification
> and battery state of health. The other sections of the passport are not
> converted by this endpoint, even at a tier that allows them. That is the case
> for labels, for the top-level carbon footprint section, for substances of
> concern, for the bill of materials, for manufacturing, for the supply chain,
> for durability and for the warranty. A preview at the `recycler` or `upstream`
> tier will therefore not make them appear. For that data, read
> `GET /v1/passport/{identifier}` at the tier that is open to you.

## Errors

The body of an error response carries a `detail` field.

| Code | Condition | What to do |
| --- | --- | --- |
| 401 | `access_tier=authority` is requested without a valid session. `detail` equals `Authority-tier access requires authentication`. | Log in with an account carrying the market surveillance authority role. A partner API key does not work. |
| 401 | `access_tier` equals `repairer`, `recycler` or `upstream`, and the call carries no valid session. `detail` equals `Professional-tier access requires authentication`. | Present an account session token. An expired token is treated as the absence of a session. Otherwise, request the `public` or `end_user` tier. |
| 403 | The call carries an `Origin` or `Referer` header that designates a domain that is not ours. `detail` equals `Forbidden origin`. | From your server, stop sending a `Referer` header, or present your session in `Authorization: Bearer <token>`. |
| 403 | The call sends neither `Origin` nor `Referer`, and carries an `access_token` cookie. `detail` equals `Origin or Referer header required`. | From your server, present the session in `Authorization: Bearer <token>` instead of the cookie. |
| 403 | `access_tier=authority` is requested by a logged-in account that does not carry that role. `detail` equals `Authority-tier access is restricted to market surveillance authorities`. | Request the tier that matches your entitlement. |
| 403 | `access_tier` equals `repairer`, `recycler` or `upstream`, and the logged-in account belongs neither to the product's brand nor to the authorities, and does not hold the matching accreditation on that brand. `detail` equals `This tier is restricted to the product's brand, partners holding the matching accreditation, and market-surveillance authorities`. | Get accredited by the product's brand, then request the tier of your trade. |
| 404 | No public passport answers to this identifier. Either no item in the catalog matches this identifier, or the item exists and carries no passport published with public visibility, neither directly nor through its model. | Do not treat this response as a failure. Check your identifier, and handle the case of an item without a public passport. A passport reserved for the owner or for the brand gives the same response, as does an item destroyed on the chain, replaced or archived. |
| 404 | The passport found refers to a brand that no longer exists. `detail` equals `Brand not found`. | Report the case to support. No action on your side corrects this state. |
| 422 | The value of `access_tier` is not one of the six accepted values. `detail` is a list, each entry carrying `loc`, `type` and `msg`. | Read `loc` to find out which parameter is at fault, then correct its value. |
| 429 | The limit of 60 calls per 60 seconds is reached for your network address, across all the `/passport` paths. `detail` equals `Rate limit exceeded: 60 requests per 60s`. | Wait the number of seconds given by `Retry-After`, then retry. Space out your calls. |
| 500 | An unexpected error occurred while processing your call. `detail` equals `Internal Server Error`. The response carries an `X-Request-Id` header. | Retry. If the error persists, contact support, giving the value of `X-Request-Id`. |

The code carries one last case, 422 with `detail` equal to
`Brand has no website_url; cannot derive a did:web issuer`. You will not meet
it: every registered brand receives an issuer identity,
`did:web:api.sealtrust.io:brand:<number>` when it has not declared its own
domain.

## See also

- [`GET /passport/{identifier}/vc`](/en/reference/get-passport-vc/),
  retrieve the signed credential of the passport, in SD-JWT-VC format.
- [`GET /passport/{identifier}/vc/verify`](/en/reference/get-passport-vc-verify/),
  check the credential's signature and read the disclosed data.
- [`GET /brand/{brand_id}/did.json`](/en/reference/get-brand-did-json/),
  retrieve a brand's public signing keys.
- [Publishing a digital product passport](/en/passeport-dpp/),
  publish, choose who sees which fields, export and have it verified.
