Method GET/passport/01/{gtin}/proof

Retrieve the public proofs of the reference passport announced by a GTIN: hash of the content, verified IPFS copy, anchoring of the document on Base and status of the signed credential. Public endpoint.

On this page

You retrieve the public proofs of the passport that this GTIN announces for a model. When you leave this page, you will know how to ask for these proofs from a GTIN alone, how to read what each block establishes, and how to interpret the absence of a block correctly.

Full address:

HTTP
GET https://api.sealtrust.io/v1/passport/01/{gtin}/proof

The same endpoint also answers without the /v1 prefix, at https://api.sealtrust.io/passport/01/{gtin}/proof. Both addresses call the same code. Use the /v1 form for a new integration.

#Authorization

None, public endpoint. You send no API key, no session and no origin header. The response is the same for everyone.

#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/01/03701234567890/proof and /passport/01/03701234567890/proof fill the same counter.

Every accepted response carries three headers.

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 for the end of the window, in seconds

A refusal returns 429, with these three headers and Retry-After. On this endpoint, Retry-After equals the duration of the window, that is 60 seconds.

#Path and query parameters

NameTypeRequiredDescription
gtinstringyesThe global trade item number, the GS1 identifier that the product code carries.

This endpoint has no query parameter.

We bring the GTIN back to its canonical 14-digit form before the lookup. We remove every character that is not a digit, then we pad with zeros on the left. A GTIN of 8, 12 or 13 digits therefore finds the same model as a 14-digit GTIN. We refuse with a 404 an empty value, a value with no digit at all, or a value of more than 14 digits.

A GTIN ends with a check digit, the digit computed from the ones that precede it. We check it, and we refuse with a 400 a GTIN whose last digit does not match. Copy the code exactly as it is printed on the product, digit for digit.

The gtin field of the response returns to you the 14-digit form that was kept.

#Headers

No header is required.

#Request body

None. This request has no body.

#Example request

Proofs of the reference passport announced by the GTIN 03701234567890.

curl -i https://api.sealtrust.io/v1/passport/01/03701234567890/proof

#Example response

HTTP code 200.

JSON
{
  "passport_version": 3,
  "data_hash": "1111111111111111111111111111111111111111111111111111111111111111",
  "ipfs_uri": "ipfs://bafybeiaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
  "ipfs_gateway_url": "https://ipfs.io/ipfs/bafybeiaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
  "passport_anchor": {
    "chain": "base",
    "chain_id": 8453,
    "tx_hash": "0x2222222222222222222222222222222222222222222222222222222222222222",
    "basescan_url": "https://basescan.org/tx/0x2222222222222222222222222222222222222222222222222222222222222222",
    "merkle_root": "0x3333333333333333333333333333333333333333333333333333333333333333",
    "leaf": "0x4444444444444444444444444444444444444444444444444444444444444444",
    "leaf_index": 7,
    "proof": [
      "0x5555555555555555555555555555555555555555555555555555555555555555",
      "0x6666666666666666666666666666666666666666666666666666666666666666"
    ],
    "anchored_at": "2026-08-14T09:12:44.318000+00:00",
    "data_hash_matches": true,
    "proves": "content_existed_at_or_before_tx"
  },
  "seal": {
    "sealed": true,
    "sealed_at": "2026-08-12T10:04:11.882000+00:00",
    "algorithm": "st-dpp-chain-v1",
    "version_hash": "7777777777777777777777777777777777777777777777777777777777777777",
    "prev_version_hash": "8888888888888888888888888888888888888888888888888888888888888888",
    "linked": true,
    "chain_link_match": true
  },
  "vc": {
    "issued": true,
    "vct": "https://schema.sealtrust.io/vct/digital-product-passport",
    "issued_at": "2026-08-12T10:04:12.140000+00:00"
  },
  "level": "model",
  "gtin": "03701234567890"
}

The response also carries the Cache-Control: no-store, max-age=0 header. Do not put this response in any shared cache. To reduce the number of your calls, keep the result in your own application cache, with the freshness window your usage tolerates.

Five fields are always present: passport_version, seal, vc, and, specific to this endpoint, level and gtin. The other fields appear only when the corresponding proof is established. An absence covers two situations that the response does not distinguish: the proof does not exist yet, or we could not establish it at the time of your call. Never read an absence as tampering.

FieldTypeDescription
passport_versionintegerThe number of the passport version served. A published correction creates a new version. When several reference versions are published and publicly visible for this model, we serve the one carrying the highest version number.
data_hashstringThe SHA-256 hash of the content of this version, 64 hexadecimal characters with no 0x prefix. Absent when the version carries none.
ipfs_uristringThe IPFS address of the frozen public copy of this passport. See below the condition that governs its presence.
ipfs_gateway_urlstringThe same copy, served by a public HTTP gateway, to open it in a browser. Present whenever ipfs_uri is present. The two fields go together.
passport_anchorobjectThe anchoring that dates the content of this version on the Base chain. Absent as long as the version has not been written to the chain, and also when we cannot rebuild the inclusion proof at the time of your call. See below.
sealobjectThe seal of the version and its place in the sequence of versions. Always present. See below.
vcobjectThe status of the signed credential of the passport. Always present. See below.
levelstringAlways model. A reminder that these proofs bear on a model.
gtinstringThe 14-digit GTIN kept after normalizing the value you sent.

#The passport_anchor block

This block establishes a single thing: the content of this version existed no later than the moment of the transaction. That is what its proves field says, whose value is content_existed_at_or_before_tx. It does not make the content true, and it does not prevent the brand from publishing a correction later.

FieldTypeDescription
chainstringThe network, base in production.
chain_idinteger or nullThe chain identifier, 8453 for Base in production. null on the oldest writes, where we had not recorded the chain; read chain in that case.
tx_hashstringThe transaction that carries the write of the root.
basescan_urlstringThe direct link to that transaction on the public explorer of the network.
merkle_rootstringThe root written to the chain, 0x followed by 64 hexadecimal characters.
leafstringThe hash of this version in the tree, 0x followed by 64 hexadecimal characters.
leaf_indexintegerThe position of this hash in the list of hashes written together. Counting starts at 0.
proofstring[]The neighboring hashes to combine with leaf to obtain merkle_root. The list is empty when the write covered only one version.
anchored_atstring or nullThe moment of the write, in ISO 8601 format. null when that moment was not recorded.
data_hash_matchesbooleantrue when the hash written at the time is still the hash of the content served today. false is the tampering signal, and it is published.
provesstringAlways content_existed_at_or_before_tx.

You can recompute the root yourself. You start from leaf. For each element of proof, in order, you place the two 32-byte values side by side, the smaller of the two first, then you apply keccak256 to the concatenation. The result becomes the new working value. After the last element of proof, you must obtain exactly merkle_root. This is the OpenZeppelin verification convention. We sort the neighbors at each level, so the proof does not need to indicate a direction.

It remains for you to check that merkle_root really is the value written to the chain. Open basescan_url to read the write transaction.

An absent passport_anchor does not mean that the passport is not reliable. Writing to the chain is an operation that SealTrust triggers. A brand does not order it from its console, and many published versions are never written. Publishing a version remains a write to the database, with no transaction on the chain. The seal block, for its part, is present on every sealed version, and it is the one that makes a rewrite visible as long as no chain write covers the version.

#The seal block

We set the seal at the first publication of a version. It chains that version to the previous one of the same passport, which makes visible a rewrite that happened afterwards.

FieldTypeDescription
sealedbooleanfalse for a version that is not sealed. The block then contains nothing else.
sealed_atstringThe moment of sealing, in ISO 8601 format.
algorithmstringThe version of the seal computation, st-dpp-chain-v1 today.
version_hashstring or nullThe chain link of this version, 64 hexadecimal characters.
prev_version_hashstring or nullThe chain link of the previous version. null for the first version of a passport.
linkedbooleantrue when the version carries a chain link.
reasonstringPresent only when linked is false, with the value sealed_before_chain. The version was published before the chaining existed, and no chain link is manufactured after the fact for a publication we cannot date.
chain_link_matchbooleanPresent only when linked is true. We recompute the chain link from the content served and we compare it with the one stored. false means that the version was modified after its publication.

#The vc block

The credential is the passport returned as a signed document, in SD-JWT-VC format. The signing private key does not leave a hardware security module.

FieldTypeDescription
issuedbooleantrue when a signed credential exists for this version. Always present.
vctstringThe identifier of the credential schema, https://schema.sealtrust.io/vct/digital-product-passport by default. Absent when the version carries none.
issued_atstringThe moment of issuance, in ISO 8601 format. Absent when that moment was not recorded.

#Why ipfs_uri can be missing

We announce the IPFS copy only when we have checked, at the time of your call, that its content is exactly the public passport the brand publishes today. We fetch the copy, we hash it, and we publish the link only if it matches.

Three situations therefore produce a response without ipfs_uri: no copy has been pinned, the copy could not be retrieved at the time of your call, or its content no longer matches what the brand publishes. A brand that changes its access rules changes what its public passport shows, so a copy pinned before that change stops being announced until it has been pinned again. The field does not distinguish these three cases. Never read its absence as proof that the copy has been altered.

#Errors

The body of an error response carries a detail field.

CodeConditionWhat to do
400The last digit of the GTIN sent is not the check digit of the digits that precede it. That is the only cause of this code on this endpoint: a value with no digit at all, or with more than fourteen digits, answers 404 and not 400. detail is Invalid GTIN: the check digit does not match.Copy the code printed on the product, digit for digit, without adding or omitting any.
404The value sent is not a usable GTIN: it is empty, it contains no digit, or it contains more than 14. detail is Unknown GS1 Digital Link.Send the GTIN as it is printed on the product, with 8, 12, 13 or 14 digits.
404No registered model carries this GTIN. detail is Unknown GS1 Digital Link.Check the GTIN with the brand.
404A model carries this GTIN, but no reference-level passport is published and publicly visible for it. detail is Unknown GS1 Digital Link.This is not a verification failure. The brand has not published a reference passport for this commercial reference, or has reserved it for a restricted audience. A passport attached to an item is never served here.
429The limit of 60 calls per 60 seconds is reached for your network address, across all /passport paths taken together. 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.
500An 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 giving the value of X-Request-Id.

#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