Method 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.
On this page
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.
#
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 |
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
Refererheader. Most HTTP libraries send none until you ask for it. - Present your session in
Authorization: Bearer <session token>. A server call that relies on theaccess_tokencookie is refused with 403Origin 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
GET https://api.sealtrust.io/v1/passport/{identifier}/vc/previewThe 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.
#Request body
None. This request has no body.
#Example request
Public preview of the credential of the item whose printed serial number is
EXEMPLE00001.
curl -i "https://api.sealtrust.io/v1/passport/EXEMPLE00001/vc/preview?access_tier=public"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());
}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.
{
"@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.
#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, retrieve the signed credential of the passport, in SD-JWT-VC format.GET /passport/{identifier}/vc/verify, check the credential's signature and read the disclosed data.GET /brand/{brand_id}/did.json, retrieve a brand's public signing keys.- Publishing a digital product passport, publish, choose who sees which fields, export and have it verified.
Was this page helpful?
Your answer opens a pre-filled email in your mail app, addressed to contact@sealtrust.io. You read it over before sending it.