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 tier, the default one. This endpoint is public at that tier.

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.

The five other 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 For a battery passport, the fields that point 1 of Annex XIII makes public are added: hazardous substances, critical raw materials, carbon footprint and its class, due diligence report, post-consumer recycled content per metal and share of renewable content. Conversely the part numbers, the battery status and the expected lifetime in cycles are removed: points 2 and 4 reserve them to persons with a legitimate interest. For a textile passport, the name and the percentage of each material are added, the fibre composition that Regulation (EU) No 1007/2011 requires on the label. For a footwear passport, the material of the upper, the lining, the insole and the sole, which Directive 94/11/EC puts on the shoe.
end_usera session, whatever the account
repairera session, and a repairer accreditation on the product's brand
recyclera session, and a recycler accreditation on the product's brand
upstreama session of an administrator of the product's brand, or the authority role
authoritya session carrying the market surveillance authority role

A session of an administrator of 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. A client, repairer or recycler account that is still tied to the brand, a former administrator for instance, is not part of it: it receives 403, or the one tier of its accreditation.

#Rate limit

600 calls per 60-second window, counted per calling IP address. The window is fixed. This limit is shared by every read (GET) whose address starts 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 600
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, the Article 13(4) and 13(5) markings, usable extinguishing agent, link to the EU declaration of conformity and its structured content once the brand has filled it in, waste management information, and for a textile or a footwear passport the label mentions described above
end_userenvironmental impact, full circularity, primary material, certified organic material, 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

The fields the console writes at the root of the passport each have their own tier, named one by one: see Passport access rules.

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-Controlpublic, max-age=60, s-maxage=300 at the public tier, private, no-store at any other tier. Only the public response, the same for every anonymous caller, may be kept by a 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. Its data describes no material and declares no substance of concern, so the evidence block rates the unit only.

JSON
{
  "id": 1,
  "product_id": 1,
  "brand_code": "K7M2QF9XBN",
  "brand_id": 42,
  "schema_version": "1.0",
  "passport_version": 3,
  "data": {
    "product_identity": {
      "gtin": "03701234567891",
      "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,
  "identity_included": true,
  "published_at": "2026-08-01T09:00:00+00:00",
  "product_name": "Cartable Exemple 32",
  "brand_name": "Exemple SAS",
  "image_url": "https://example-sas.test/images/cartable-32.jpg",
  "gtin": "03701234567891",
  "gs1_digital_link": "https://id.gs1.org/01/03701234567891/21/EXEMP1E00001",
  "passport_service_provider": null,
  "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"
    },
    "legend": {
      "declared": "Stated by the brand. Recorded, dated and attributable, but not independently checked.",
      "verified": "Rests on an on-chain reference that anyone can re-check independently. SealTrust records it and does not re-query the chain on each request."
    },
    "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 batch or the model and applies to all of their units. A unit with no passport of its own receives the one of its production batch, failing that the one of its model.
brand_codestringThe public identifier of the brand, ten characters. This is the one to quote and to keep.
brand_idintegerThe internal number of the brand. ⚠️ Kept so existing integrations do not break; prefer brand_code.
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. See the note below: filled in at the public and end_user tiers for passports published since 14 July 2026, null for older ones.
ipfs_gateway_urlstringThe HTTP address that copy reads from. Follows exactly the same rule as ipfs_uri.
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.
identity_includedbooleantrue when the unit has its identity: an owner, a chip, the claim and the transfer. A unit created on the Compliance + Identity offer keeps it, even if its brand later moves to the Compliance offer. false for a unit without identity. null when the answer does not say, which never means a refusal.
available_tiersarray of stringThe tiers YOUR call may ask for on this passport, in the order public, end_user, repairer, recycler, upstream, authority: ["public"] without authentication, end_user added for a signed-in account, the trade the brand accredited for a partner, all of them for an authority. The list grants nothing: every tier asked is still checked. A response carrying it for a signed-in account must go to no shared cache.
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_urlstring | nullThe full address of the photograph of the piece's model: its main photo, or else the first image of its media library. Computed as on the passport of the model and of the lot. null when the model has none, or when it belongs to a brand other than this passport's. It is the light copy of the photo (WebP, 800 pixels at most on the long side, 100 KB at most, no EXIF data) when it exists, else the original. An address of another site is returned as given.
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.
passport_service_providerobject or nullThe passport service provider keeping the back-up copy of this version (Regulation (EU) 2024/1781, article 10(4) and Annex III point (l)): identifier, its reference, name, its legal name, and backed_up_at, the date of the copy in ISO 8601 format. null until the copy of this version exists, or when its last check failed. Outside data, so outside the data_hash fingerprint.
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
verifiedRests on an on-chain reference that anyone can re-check independently. SealTrust records it and does not re-query the chain on each request.
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 can carry one of these values: identity, integrity, composition and substances_of_concern. Only the sections the passport actually contains receive one.

identity and integrity are always present, because they bear on the unit itself. 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.

composition and substances_of_concern appear only when the passport data carries them. A passport that describes no material receives no composition status, and sections then holds two entries. A materials block left empty (rows with no name, no share and no origin) describes no material. The number of entries therefore ranges from two to four depending on the passport served.

The block additionally carries legend, which restates the meaning of the values present, derived at true, and note.

Display a status only for the sections that sections names. An absent section is neither verified nor disproved, it is simply not rated.

#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. The back-up copy provider appears under espr:passportServiceProvider (identifier, name, espr:backedUpAt), on the same terms as passport_service_provider, and is absent until the copy exists.

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=end_user is requested with no valid session. detail is End-user-tier access requires authentication.Sign in with any account, or request the public tier.
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 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 is neither an administrator of the product's brand nor the holder of 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.
404The call arrives on a brand's verified domain name, and the product belongs to another brand. detail is Product not found.Call this address on the domain of the brand concerned, or on api.sealtrust.io. The message is the same as for an unknown identifier, so as not to confirm that it exists.
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 600 calls have been made from your IP address to a /passport read within the current 60-second window. detail is Rate limit exceeded: 600 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.

#Why the IPFS copy is not always given at the public tier

An IPFS copy is readable by anyone who knows its content identifier. Until 14 July 2026 we pinned the full passport there, restricted fields included. Handing the address to a public reader would have bypassed the access tiers, so it was hidden from everyone except the authenticated tiers.

Since that date we pin only the public projection of the passport: the copy no longer holds anything restricted, and its address is given to everyone. Passports published before that date keep a full-data copy, for ever, because a content identifier cannot be withdrawn. Their address therefore stays restricted to the authenticated tiers.

Measured in production on 12 September 2026: of sixteen passports carrying an IPFS copy, five predate that change.

#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