Method GET/passport /{identifier} /vc
Retrieve the signed credential of a passport, in SD-JWT-VC format, filtered to the access tier asked for. Public endpoint at the public and end_user tiers.
On this page
You retrieve the passport of a product as a digital credential signed by the brand. When you leave this page, you will know how to ask for that credential at the access tier that concerns you, how to read the six fields of the response, and where to find the public key that lets you check its signature without trusting us.
Full address:
GET https://api.sealtrust.io/v1/passport/{identifier}/vcThe same endpoint also answers without the /v1 prefix, at
https://api.sealtrust.io/passport/{identifier}/vc. Both addresses call the
same code. Use the /v1 form for a new integration.
The response is a JSON object of six fields. The passport itself sits in a
single field, as a character string signed in the SD-JWT-VC format. To read the
passport data as directly usable JSON, call GET /v1/passport/{identifier}.
#
None for the public and end_user tiers. This endpoint is public at those two
tiers.
A partner API key opens nothing here. The tiers that ask for an identity are opened with a user account session token, never with an API key.
Four values of the access_tier parameter require an account session, which you
present through the Authorization: Bearer <session token> header or through
the session cookie set at sign-in.
| Tier asked for | What it takes |
|---|---|
public | nothing |
end_user | nothing |
repairer | a session, and an active repairer accreditation on the brand of the product, or access to that brand, or the authority role |
recycler | a session, and an active recycler accreditation on the brand of the product, or access to that brand, or the authority role |
upstream | a session with access to the brand of the product, or the authority role. No accreditation opens this tier. |
authority | a session carrying the market surveillance authority role |
A session with access to the brand of the product opens the three professional tiers on the products of that brand.
The access tier rules are the same as those of the
GET /v1/passport/{identifier} endpoint. This path therefore never gives access
to more fields than the JSON read.
One difference separates the two paths. This endpoint only serves the passports whose visibility is public. A passport reserved for the owner or reserved for the brand is never returned here, not even to its owner.
#The origin check
Your server-to-server calls go through as they are. Two situations give a 403.
A call issued by a web page opened on a domain that is not ours carries an
Origin or Referer header that we refuse. So do not call this endpoint from
the browser of a visitor, call it from your server.
A call that carries the session cookie without an Origin or Referer header
is refused as well. The session cookie only holds from a page served by one of
our domains. From a server or from the command line, present the token in
Authorization: Bearer.
#Rate limit
60 calls per 60-second slice, counted per calling network address. The window is fixed.
That counter is shared by every path that starts with /passport. The calls you
address to one of them eat into the budget of the others. The /v1 prefix does
not create a second budget: /v1/passport/EXEMP1E00001/vc and
/passport/EXEMP1E00001/vc fill the same counter.
Every accepted response carries three headers.
| Header | Content |
|---|---|
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 since January 1, 1970 |
Going over returns 429, with those three headers and Retry-After. On this
endpoint, Retry-After is the duration of the window, that is 60 seconds.
#Path and query parameters
| Name | Type | Required | Description |
|---|---|---|---|
identifier | string | yes | The product whose credential you want. Three forms are accepted, see below. |
access_tier | string | no | The access tier asked for. Defaults to public. Six accepted values, listed further down. |
No header is required in the request.
#The three accepted forms of identifier
| Form | What it looks like | Where you find it |
|---|---|---|
| Serial number | 12 characters, digits and uppercase letters. The letters I, L, O and U never appear in it. | Printed on the product, it is what its QR code carries |
| Token identifier | A run of digits, often very long | Returned by our responses in the token_id field |
| Identifier hash | 0x followed by 64 hexadecimal characters | Returned by our responses in the uid_hash field |
The identifier hash exists for a QR-only product just as for a product with an NFC chip. The server draws it at random for a QR product, it derives it from the identifier of the chip for an NFC product.
The server recognizes the form from the way it is written. It looks up a value
that starts with 0x and is exactly 66 characters long as an identifier hash.
It looks up any other value first as a token identifier. It tries the serial
number last, when the first two lookups have returned nothing.
The server recognizes the identifier hash whatever the case. It also recognizes
the serial number whatever the case, and it canonicalizes it the way the QR
resolver does: it reads the letters I and L as a 1, the letter O as a
0. You can therefore send it a number copied by hand from a label.
This endpoint only resolves the products still in the catalog of the brand. An item destroyed on chain, replaced by a later version or withdrawn without replacement answers 404.
#The six values of access_tier
These tiers are different audiences, with no hierarchy between them. A recycler is not above a repairer. Each of the three professional tiers inherits the public tier and the end user tier, then adds what its trade calls for.
| Value | What the credential reveals |
|---|---|
public | product identification, ESPR compliance, REACH and CE marking, recyclability percentage, recycled content percentage, labels, general battery specification. These fields appear in the clear in the signed token, no disclosure is attached. |
end_user | environmental impact, full circularity, primary material, certified organic material, durability, energy efficiency, carbon footprint |
repairer | bill of materials, dismantling instructions, repairability index, battery state of health |
recycler | material composition, substances of concern, dismantling instructions, battery state of health |
upstream | material composition, substances of concern, manufacturing, supply chain |
authority | every field of the document |
A brand can replace these rules with its own. The table above describes what applies in the absence of rules of the brand's own. The same rules apply here and on the JSON read.
The server refuses any other value with 422. It returns the tier actually served
in the access_tier field of the response and in the X-DPP-Access-Tier
header. Read one of the two rather than assuming it.
#Request body
None. This request has no body.
#Example request
Public credential of the product whose printed number is EXEMP1E00001.
curl -s "https://api.sealtrust.io/v1/passport/EXEMP1E00001/vc?access_tier=public"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);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]))The three tabs call the same address with the same values. The curl tab writes
the raw response to standard output. The TypeScript and Python tabs extract the
same fields from it, in the same order.
#Example response
HTTP status code 200.
The values below are fictitious. The signed token and the disclosure segments are shortened, a real token is several thousand characters long.
{
"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~"
}| Field | Type | Presence | Description |
|---|---|---|---|
passport_id | integer | always | The identifier of the passport version this credential relates to. |
issuer | string | always | The did:web decentralized identifier of the brand that signed. It is what leads to the public verification key. Always filled in on this endpoint. |
vct | string | always | The identifier of the credential template. Is https://schema.sealtrust.io/vct/digital-product-passport in the absence of a value recorded on the passport. |
access_tier | string | always | The tier actually served. |
format | string | always | Always dc+sd-jwt. It is the media type of the selective disclosure credential. |
sd_jwt_vc | string | always | The credential itself. See below. |
#Response headers worth knowing
| Header | Content |
|---|---|
X-DPP-Access-Tier | the tier actually served |
Cache-Control | no-store, max-age=0, whatever the tier served. Do not put this response behind any shared cache. |
#Reading the sd_jwt_vc field
The content of sd_jwt_vc is a run of segments separated by the ~ character.
The last segment is always empty, so the string ends with a ~.
<jeton signé>~<divulgation>~<divulgation>~The first segment is a signed token in three parts, separated by dots. The header and the payload are encoded in base64url, without padding. You decode them without a key.
The header carries three values.
| Value | Content |
|---|---|
alg | ES256. The signature is an ECDSA signature on curve P-256. |
typ | dc+sd-jwt |
kid | The identifier of the key that signed, in the form <brand did>#key-<version number>. |
The payload carries the following fields.
| Field | Content |
|---|---|
iss | The did:web identifier of the issuing brand. It holds the same value as the issuer field of the response. |
vct | The identifier of the credential template. |
iat | The issuance date, in seconds since January 1, 1970. |
@context | ["https://www.w3.org/ns/credentials/v2", "https://schema.sealtrust.io/dpp/v1"] |
type | ["VerifiableCredential", "DigitalProductPassport"] |
issuer | A repeat of iss, expected by the verifiable credentials data model. |
validFrom | The issuance date in ISO 8601 format, to the second, in universal time. |
credentialSubject | The passport data. The public fields appear in the clear in it. The others are replaced by hashes, under the _sd key. |
credentialSchema | An object with two keys, id which repeats vct, and type which is JsonSchema. |
product | The identity of the product: uid_hash, token_id and name. Never masked. |
brand | The identity of the brand: name, lei_code, eori_number, website_url, postal_address, contact_email. Never masked. The values that are not filled in are absent. |
A passport can cover a product model or one specific item. When the brand
publishes a model passport, it holds for every item that shares the same product
code, and the credential issued at that publication carries uid_hash and
token_id at null in the product block: it designates no item in
particular.
The following segments are the disclosures. Each one is an array of three elements encoded in base64url: a salt, the name of the field, its value. You decode them without a key. Their number depends on the tier asked for.
WyJFWEVNUExFMDAwMDAwMDAwMDAwMDAwMCIsInJlcGFpcmFiaWxpdHlfaW5kZXgiLDguMl0That example segment decodes to
["EXEMPLE0000000000000000", "repairability_index", 8.2].
At the public tier, the response reveals nothing more than the fields always
present in the signed document. The string is then reduced to the signed token
followed by a ~. Each other tier adds the disclosures that concern it.
#Check the signature yourself
The issuer field carries a did:web identifier. It designates a public
document that contains the public keys of the brand, expressed as
JsonWebKey2020. The key to use is the one whose identifier matches the kid
of the token header. That document contains only the keys that are not revoked,
so a revoked key no longer appears in it.
An identifier of the form did:web:api.sealtrust.io:brand:4242 resolves to
https://api.sealtrust.io/brand/4242/did.json. An identifier of the form
did:web:id.exemple-sas.example resolves to
https://id.exemple-sas.example/.well-known/did.json. A brand can host that
document itself on its own domain, in which case the verification of its
passports depends on none of our servers.
If you prefer the verification to be done for you, call
GET /v1/passport/{identifier}/vc/verify.
#Errors
The body of an error response carries a detail field.
| Code | Condition | What to do |
|---|---|---|
| 401 | You ask for authority without a session. detail is Authority-tier access requires authentication. | Sign in, then present the session token in Authorization: Bearer. |
| 401 | You ask for repairer, recycler or upstream without a session. detail is Professional-tier access requires authentication. | Sign in, then present the session token in Authorization: Bearer. An API key will not do. |
| 403 | You ask for authority with a session that does not carry that role. detail is Authority-tier access is restricted to market surveillance authorities. | Ask for a tier that matches your situation. |
| 403 | You ask for a professional tier without an active accreditation on the brand of the product, without access to that brand and without the authority role. detail starts with This tier is restricted to the product's brand. | Ask the brand to accredit you, then ask again for the tier that matches your trade. |
| 403 | The call carries an Origin or Referer header that does not designate one of our domains, which happens for any call issued from a web page hosted elsewhere. detail is Forbidden origin. | Call this endpoint from your server, never from the browser of a visitor. |
| 403 | The call carries a session cookie without an Origin or Referer header. detail is Origin or Referer header required. | Present the token in Authorization: Bearer instead of the session cookie. |
| 404 | No product in the catalog matches this identifier, under any of the three accepted forms. detail is Product not found. | Check the identifier. An item destroyed on chain, replaced by a later version or withdrawn gives this same response. |
| 404 | The product exists, but no passport published with public visibility is attached to it, neither directly nor through its model. detail is No published passport found for this product. | Do not treat this response as a failure. This product has no public passport. A passport reserved for the owner or for the brand gives the same response. |
| 404 | The passport exists and it is public, but no signed credential has been issued for it yet. detail starts with No VC issued for this passport yet. | Read the passport in JSON with GET /v1/passport/{identifier}. Publishing a version of a passport issues its credential: ask the brand to republish the current version. |
| 404 | The brand of the passport is not resolvable. detail is Brand not found. | Report it to support. No action on your side changes this response. |
| 422 | The value of access_tier is not one of the six accepted. detail is a list of objects that name the parameter at fault. | Correct the value. The six accepted values are listed above. |
| 429 | The limit of 60 calls per 60 seconds is reached for your network address, across every /passport path. detail is Rate limit exceeded: 60 requests per 60s. | Wait the number of seconds indicated by Retry-After, then try again. Cache the response on your side. |
| 500 | An unexpected error occurred while your call was being processed. detail is Internal Server Error. The response carries an X-Request-Id header. | Try again. If the error persists, contact support quoting the value of X-Request-Id. |
#See also
GET /passport/{identifier}/vc/preview, see, without a signature, what an access tier would expose.GET /passport/{identifier}/vc/verify, check the signature of the credential and read the data revealed.GET /brand/{brand_id}/did.json, retrieve the public signing keys of a brand.- Publish 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.