Method GET/passport /{identifier}
Read a product's published digital passport from its printed number, its token identifier or its chip hash, at the requested access tier. Public endpoint.
On this page
- Authorization
- Rate limit
- Path and query parameters
- The three accepted identifier forms
- The six values of access_tier
- Response headers to know about
- Request body
- Example request
- Example response
- The fields of the response
- The warranty block
- The evidence block
- The lifecycle block
- The integrity block
- The JSON-LD response
- Errors
- See also
You read the published digital passport of a single product. By the end of this page, you will know how to retrieve its data at the access tier you request, read its warranty, know on what basis each section can be believed, and recognize a product withdrawn from the catalog.
Full address:
GET https://api.sealtrust.io/v1/passport/{identifier}The same endpoint also answers without the /v1 prefix, at
https://api.sealtrust.io/passport/{identifier}. Both addresses call the same
code. Use the /v1 form for a new integration.
#
None for the public and end_user tiers. This endpoint is public.
A partner API key opens nothing here. The token received in the Authorization
header is decoded as a user account session token, and an API key is not one:
the read fails silently and the call proceeds as an anonymous call.
Four values of the access_tier parameter do, on the other hand, require an
account session, presented through the Authorization: Bearer <session token>
header or through the session cookie set at sign-in.
| Tier requested | What is needed |
|---|---|
public | nothing |
end_user | nothing |
repairer | a session, and a repairer accreditation on the product's brand |
recycler | a session, and a recycler accreditation on the product's brand |
upstream | a session with access to the product's brand, or the authority role |
authority | a session carrying the market surveillance authority role |
A session with access to the product's brand opens the three trade tiers on its own products. A session carrying the market surveillance authority role opens them as well.
#Rate limit
60 calls per 60-second window, counted per calling IP address. The window is
fixed. This limit is shared by every address starting with /passport, and the
/passport/… and /v1/passport/… forms feed the same counter.
Every accepted response carries three headers that describe this counter.
| 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 |
Going over returns 429, with the same three headers and a Retry-After in
seconds.
#Path and query parameters
| Name | Type | Required | Description |
|---|---|---|---|
identifier | string | yes | The product's identifier. Three forms are accepted, see below. |
access_tier | string | no | The access tier requested. Is public by default. Six accepted values, listed further down. |
verify_integrity | boolean | no | Is false by default. At true, the server retrieves the passport's IPFS copy, compares its hash, and adds an integrity block to the response. |
format | string | no | Absent by default, and the server then returns the JSON described below. The value jsonld returns the same filtered content, expressed in Schema.org and GS1. The server ignores any other value and returns the default response. |
#The three accepted identifier forms
| 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 string of digits, often very long | Returned by our responses in the token_id field |
| Chip hash | 0x followed by 64 hexadecimal characters | Returned by our responses in the uid_hash field |
The server recognizes the chip hash whatever the case. It recognizes the serial
number the same way, and it canonicalizes it the way the QR resolver does: it
reads the letters I and L as a 1, and the letter O as a 0. So you can
retype by hand the number read off a label, even if you confuse those
characters.
The server recognizes the form from the way it is written. It looks for a value
that starts with 0x and is exactly 66 characters long as a chip hash. It looks
for any other value first as a token identifier, then, if that lookup gives
nothing, as a serial number.
#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 trade tiers inherits the public tier and the end user tier, then adds what its trade requires.
| Value | What it adds to the fields of data |
|---|---|
public | product identification, ESPR compliance, REACH and CE marking, recyclability percentage, recycled content percentage, labels, general battery specification |
end_user | environmental impact, full circularity, primary material, certified organic cotton, durability, energy efficiency, carbon footprint |
repairer | bill of materials, disassembly instructions, repairability index, battery state of health |
recycler | material composition, substances of concern, disassembly instructions, battery state of health |
upstream | material composition, substances of concern, manufacturing, supply chain |
authority | the entirety of the data, with no filtering |
A brand can replace these rules with its own, by product category. The table above describes what applies in the absence of rules specific to the brand.
The server refuses any other value with a 422. It returns the tier actually
served in the access_tier field and in the X-DPP-Access-Tier header of the
default JSON response. Read one of the two rather than assuming it. With
format=jsonld, neither that field nor that header exists, see below.
#Response headers to know about
| 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. |
The server sets X-DPP-Access-Tier only on the default JSON response.
Cache-Control carries the same value on both formats.
No header is required in the request.
#Request body
None. This request has no body.
#Example request
Reading the public passport of the product whose printed number is
EXEMP1E00001. The three examples make the same call, stop the program on an
error response, then display the same three values:
passport_version, access_tier and data.product_identity.
curl --fail-with-body -s \
"https://api.sealtrust.io/v1/passport/EXEMP1E00001?access_tier=public" \
| jq '{passport_version, access_tier, product_identity: .data.product_identity}'const identifiant = "EXEMP1E00001";
const url = new URL(
`https://api.sealtrust.io/v1/passport/${encodeURIComponent(identifiant)}`,
);
url.searchParams.set("access_tier", "public");
const response = await fetch(url, { method: "GET" });
if (!response.ok) {
throw new Error(`SealTrust a répondu ${response.status}`);
}
const passeport = await response.json();
console.log({
passport_version: passeport.passport_version,
access_tier: passeport.access_tier,
product_identity: passeport.data.product_identity,
});import requests
from urllib.parse import quote
identifiant = "EXEMP1E00001"
response = requests.get(
f"https://api.sealtrust.io/v1/passport/{quote(identifiant, safe='')}",
params={"access_tier": "public"},
timeout=30,
)
response.raise_for_status()
passeport = response.json()
print(
{
"passport_version": passeport["passport_version"],
"access_tier": passeport["access_tier"],
"product_identity": passeport["data"]["product_identity"],
}
)In the curl example, --fail-with-body returns a non-zero exit code when the
server answers an error, and still displays the body. The jq tool only serves
to read the JSON in the terminal, it takes no part in the call.
#Example response
HTTP code 200.
This product is still in the catalog, it has not been claimed by a customer, and the passport is requested at the public tier.
{
"id": 1,
"product_id": 1,
"brand_id": 42,
"schema_version": "1.0",
"passport_version": 3,
"data": {
"product_identity": {
"gtin": "03701234567890",
"model": "Cartable Exemple 32",
"brand": "Exemple SAS",
"made_in": "FR"
},
"compliance": {
"eu_espr": true,
"reach": true,
"ce_marking": false
},
"circularity": {
"recyclability_percentage": 62,
"recycled_content_percentage": 0
}
},
"data_hash": "0000000000000000000000000000000000000000000000000000000000000000",
"ipfs_uri": null,
"ipfs_gateway_url": null,
"visibility": "public",
"access_tier": "public",
"is_owner": false,
"published_at": "2026-08-01T09:00:00+00:00",
"product_name": "Cartable Exemple 32",
"brand_name": "Exemple SAS",
"image_url": "https://exemple-sas.test/images/cartable-32.jpg",
"gtin": "03701234567890",
"gs1_digital_link": "https://id.gs1.org/01/03701234567890/21/EXEMP1E00001",
"warranty": {
"status": "active",
"ends_at": "2028-08-01T09:00:00+00:00",
"duration_months": 24,
"transferable": true,
"remaining_days": 730
},
"evidence": {
"sections": {
"identity": "verified",
"integrity": "declared",
"composition": "declared",
"substances_of_concern": "declared"
},
"legend": {
"declared": "Stated by the brand. Recorded, dated and attributable, but not independently checked.",
"verified": "Checked mechanically against a public record; no declaration involved."
},
"derived": true,
"note": "Statuses are computed by SealTrust from verifiable facts. A brand cannot set them."
}
}The domain of the gs1_digital_link field is that of the resolver configured for
your integration. https://id.gs1.org is only the fallback value, used when no
resolver is configured. Do not hard-code this domain, read the value returned.
#The fields of the response
| Field | Type | Description |
|---|---|---|
id | integer | The identifier of the passport version served. |
product_id | integer | The unit to which this passport is attached, or null when the passport covers the model and applies to all of its units. |
brand_id | integer | The number of the brand the passport belongs to. |
schema_version | string | The version of the passport's data schema. |
passport_version | integer | The published version number. It goes up with each new publication. |
data | object | The passport's data, filtered according to the tier served. Its shape depends on the product category. |
data_hash | string or null | The hash of this version's complete data, 64 hexadecimal characters. null when no hash has been recorded for this version. |
ipfs_uri | string | The IPFS address of the passport's copy. Always null at the public and end_user tiers. |
ipfs_gateway_url | string | The HTTP address through which this copy is read. Always null at the public and end_user tiers. |
visibility | string | The visibility of the version served: public, or owner_only when the current owner is authenticated. The brand_only visibility is never served here. |
access_tier | string | The tier actually served, which may differ from the tier requested for the product's owner. |
is_owner | boolean | true when the call is authenticated and the account is the unit's current owner. |
published_at | string | Date and time this version was published, in ISO 8601 format, or null. |
product_name | string or null | The product's name. null when no name has been recorded on the item. |
brand_name | string | The brand's name, or null if the product is attached to none. |
image_url | string | The model's photograph, or null. |
gtin | string | The model's GTIN, brought back to 14 digits. null when the model carries none, or when the recorded value is not a valid GTIN. |
gs1_digital_link | string | The GS1 Digital Link that identifies this unit, of the form <resolution domain>/01/<14-digit gtin>/21/<serial number>. null when the GTIN or the serial number is missing. |
warranty | object | The warranty summary, or null when the product has none. See below. |
evidence | object | On what basis each section can be believed. See below. Absent if its computation fails. |
lifecycle | object | Present only when the unit is destroyed or out of the catalog. See below. |
integrity | object | Present only when verify_integrity=true and the IPFS link is served at your tier. See below. |
#The warranty block
| Field | Type | Description |
|---|---|---|
status | string | active, expiring_soon, expired or void. Recomputed on each read. |
ends_at | string | End date, in ISO 8601 format, or null for a lifetime warranty. |
duration_months | integer | The announced duration, in months. |
transferable | boolean | true when the warranty follows the product on a change of owner. |
remaining_days | integer | Whole days remaining. Negative when the warranty has passed. null for a lifetime or voided warranty. |
#The evidence block
Three values exist, and they are computed by SealTrust. A brand cannot choose them.
| Value | What it says |
|---|---|
verified | Checked mechanically against a public record, with no declaration from anyone. |
document_backed | A third-party document is attached and can be retrieved. Its content has not been audited by SealTrust. |
declared | Declared by the brand. Recorded, dated, attributable, not independently verified. |
Four sections carry one of these values: identity, integrity, composition
and substances_of_concern. The identity section goes to verified when the
unit carries a token identifier on the chain. The integrity section goes to
verified when this version's hash has been anchored and still matches the
recorded data. The block additionally carries legend, which restates the
meaning of the values present, derived at true, and note.
#The lifecycle block
It appears only if the unit is destroyed or out of the catalog. Its passport is still served so that the identifier keeps resolving.
{
"lifecycle": {
"status": "superseded",
"is_burned": false,
"superseded": true,
"note": "This unit is superseded or withdrawn; its passport is retained so the identifier stays resolvable (EN 18219 §4.2.2 persistence)."
}
}Read is_burned before status.
The status field is superseded when the unit has been replaced by another,
and archived when it was withdrawn without replacement. The superseded field
is true only for the first case.
The block also appears when the unit has been destroyed, that is, when
is_burned is true. In that case status carries the product's current
state, which may be null or an active value. So never infer destruction from
the value of status.
#The integrity block
{
"integrity": {
"ipfs_fetched": true,
"ipfs_hash": "1111111111111111111111111111111111111111111111111111111111111111",
"expected_hash": "1111111111111111111111111111111111111111111111111111111111111111",
"match": true
}
}| Field | Type | Description |
|---|---|---|
ipfs_fetched | boolean | true when the server managed to read the IPFS copy. |
ipfs_hash | string | The hash of the content actually read on IPFS. |
expected_hash | string | The hash expected for this content. |
match | boolean | The verdict of the comparison. |
expected_hash is the hash of the PUBLIC projection of the passport, the one
deposited on IPFS. It differs from data_hash, which covers the complete data,
including the fields reserved for the professional tiers. The two values coincide
only when the passport carries no non-public field. So never compare
expected_hash and data_hash.
The match field is true when the IPFS copy matches, false when it differs,
and null when the copy could not be retrieved. In that last case
ipfs_fetched is false, an error field replaces the two hashes, and null
means that nothing could be concluded.
#The JSON-LD response
With format=jsonld, the response carries the content type
application/ld+json. It is a Schema.org and GS1 document whose fields are
filtered by the same access tier. It contains neither passport_version, nor
data_hash, nor the warranty, evidence, lifecycle and integrity blocks
described above: the warranty is expressed there as WarrantyPromise, and the
other blocks do not appear in it.
Two other differences matter for your integration.
The document carries no access_tier field. The server does not return the
X-DPP-Access-Tier header either. To know the tier actually served, call
without format, or stick to the tier you requested.
The server ignores verify_integrity in this format. It returns the JSON-LD
document before computing the integrity block, so this parameter changes
nothing in the response and no error signals it to you.
#Errors
The body of an error response carries a detail field.
| Code | Condition | What to do |
|---|---|---|
| 401 | access_tier=authority is requested with no valid session. detail is Authority-tier access requires authentication. | Sign in with an account carrying the market surveillance authority role. A partner API key will not do. |
| 401 | access_tier is repairer, recycler or upstream, and the call carries no valid session. detail is Professional-tier access requires authentication. | Present an account session token, or request the public or end_user tier. |
| 403 | access_tier=authority is requested by a signed-in account that does not carry that role. detail is Authority-tier access is restricted to market surveillance authorities. | Request the tier that matches your authorization. |
| 403 | A professional tier is requested by a signed-in account that has neither access to the product's brand nor the matching accreditation on that brand. detail is This tier is restricted to the product's brand, partners holding the matching accreditation, and market-surveillance authorities. | Ask the brand for the accreditation that matches your trade, then request the tier of that trade. |
| 404 | No product matches the identifier, under any of the three accepted forms. detail is Product not found. | Check the number you copied. A destroyed product or one withdrawn from the catalog is still resolved here, so this response does mean that the identifier is unknown. |
| 404 | The product exists, but no published passport version matches it. detail is No published passport found for this product. | The brand must publish a version. An unpublished draft is never served, and neither is a version with brand_only visibility. |
| 422 | A parameter value is refused: an access_tier that is not one of the six values, or a verify_integrity that is not a boolean. detail is a list, each entry carrying loc, type and msg. | Read loc to find out which parameter is at fault, then fix its value. |
| 429 | More than 60 calls have been made from your IP address to a /passport address within the current 60-second window. detail is Rate limit exceeded: 60 requests per 60s. | Wait the number of seconds indicated by the Retry-After header, then try again. |
| 500 | An unexpected error occurred while processing your call. detail is Internal Server Error. | Try again. If the error persists, contact support giving the time of the call and the value of the response's X-Request-Id header. |
#See also
GET /passport/01/{gtin}, read a model's published passport, from its GTIN.GET /passport/{identifier}/verify, check the integrity of an item's published passport.GET /passport/{identifier}/proof, gather the public proofs of an item's passport.GET /passport/{identifier}/vc, retrieve the passport's signed credential, in SD-JWT-VC format.- 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.