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

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:

HTTP
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.

#Authorization

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 requestedWhat is needed
publicnothing
end_usernothing
repairera session, and a repairer accreditation on the product's brand
recyclera session, and a recycler accreditation on the product's brand
upstreama session with access to the product's brand, or the authority role
authoritya 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.

HeaderContent
X-RateLimit-Limitthe limit applied over the window, here 60
X-RateLimit-Remainingwhat is left to you in the current window
X-RateLimit-Resetthe 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

NameTypeRequiredDescription
identifierstringyesThe product's identifier. Three forms are accepted, see below.
access_tierstringnoThe access tier requested. Is public by default. Six accepted values, listed further down.
verify_integritybooleannoIs false by default. At true, the server retrieves the passport's IPFS copy, compares its hash, and adds an integrity block to the response.
formatstringnoAbsent 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

FormWhat it looks likeWhere you find it
Serial number12 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 identifierA string of digits, often very longReturned by our responses in the token_id field
Chip hash0x followed by 64 hexadecimal charactersReturned 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.

ValueWhat it adds to the fields of data
publicproduct identification, ESPR compliance, REACH and CE marking, recyclability percentage, recycled content percentage, labels, general battery specification
end_userenvironmental impact, full circularity, primary material, certified organic cotton, durability, energy efficiency, carbon footprint
repairerbill of materials, disassembly instructions, repairability index, battery state of health
recyclermaterial composition, substances of concern, disassembly instructions, battery state of health
upstreammaterial composition, substances of concern, manufacturing, supply chain
authoritythe 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

HeaderContent
X-DPP-Access-Tierthe tier actually served
Cache-Controlno-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}'

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.

JSON
{
  "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

FieldTypeDescription
idintegerThe identifier of the passport version served.
product_idintegerThe unit to which this passport is attached, or null when the passport covers the model and applies to all of its units.
brand_idintegerThe number of the brand the passport belongs to.
schema_versionstringThe version of the passport's data schema.
passport_versionintegerThe published version number. It goes up with each new publication.
dataobjectThe passport's data, filtered according to the tier served. Its shape depends on the product category.
data_hashstring or nullThe hash of this version's complete data, 64 hexadecimal characters. null when no hash has been recorded for this version.
ipfs_uristringThe IPFS address of the passport's copy. Always null at the public and end_user tiers.
ipfs_gateway_urlstringThe HTTP address through which this copy is read. Always null at the public and end_user tiers.
visibilitystringThe visibility of the version served: public, or owner_only when the current owner is authenticated. The brand_only visibility is never served here.
access_tierstringThe tier actually served, which may differ from the tier requested for the product's owner.
is_ownerbooleantrue when the call is authenticated and the account is the unit's current owner.
published_atstringDate and time this version was published, in ISO 8601 format, or null.
product_namestring or nullThe product's name. null when no name has been recorded on the item.
brand_namestringThe brand's name, or null if the product is attached to none.
image_urlstringThe model's photograph, or null.
gtinstringThe 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_linkstringThe 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.
warrantyobjectThe warranty summary, or null when the product has none. See below.
evidenceobjectOn what basis each section can be believed. See below. Absent if its computation fails.
lifecycleobjectPresent only when the unit is destroyed or out of the catalog. See below.
integrityobjectPresent only when verify_integrity=true and the IPFS link is served at your tier. See below.

#The warranty block

FieldTypeDescription
statusstringactive, expiring_soon, expired or void. Recomputed on each read.
ends_atstringEnd date, in ISO 8601 format, or null for a lifetime warranty.
duration_monthsintegerThe announced duration, in months.
transferablebooleantrue when the warranty follows the product on a change of owner.
remaining_daysintegerWhole 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.

ValueWhat it says
verifiedChecked mechanically against a public record, with no declaration from anyone.
document_backedA third-party document is attached and can be retrieved. Its content has not been audited by SealTrust.
declaredDeclared 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.

JSON
{
  "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

JSON
{
  "integrity": {
    "ipfs_fetched": true,
    "ipfs_hash": "1111111111111111111111111111111111111111111111111111111111111111",
    "expected_hash": "1111111111111111111111111111111111111111111111111111111111111111",
    "match": true
  }
}
FieldTypeDescription
ipfs_fetchedbooleantrue when the server managed to read the IPFS copy.
ipfs_hashstringThe hash of the content actually read on IPFS.
expected_hashstringThe hash expected for this content.
matchbooleanThe 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.

CodeConditionWhat to do
401access_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.
401access_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.
403access_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.
403A 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.
404No 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.
404The 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.
422A 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.
429More 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.
500An 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

Your answer opens a pre-filled email in your mail app, addressed to contact@sealtrust.io. You read it over before sending it.

Suggest a correctionReport a problem