Method GET/passport/{identifier}/proof

Gather in one call every public proof of a passport: content hash, IPFS copy, anchor on Base, version seal and state of the signed credential. Public endpoint.

On this page

You gather in a single call the public proofs attached to a product's passport. By the end of this page, you will know how to ask for these proofs from an item identifier, read what each block actually establishes, and tell a missing proof from a failing proof.

Full address:

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

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

This endpoint returns no passport content. It returns the elements that let you verify that content. To read the passport itself, call GET /v1/passport/{identifier}.

#Authorization

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

You receive here only the passports whose visibility is public. A passport reserved for the owner or reserved for the brand is never returned to you by this endpoint.

#Rate limit

60 calls per 60 second slice, counted per calling network address.

This counter is shared by every path that starts with /passport. The calls you make to one of them therefore eat into the budget of the others. The /v1 prefix does not create a second budget: /v1/passport/1042/proof and /passport/1042/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 you have left in the current window
X-RateLimit-Resetthe timestamp of 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
identifierstringyesThe item whose proofs you want. Three forms are accepted, see below.

This endpoint has no query parameter.

identifier accepts three forms, tried in this order.

FormAppearanceWhere it comes from
Identifier hash0x followed by 64 hexadecimal charactersthe hash of the unique identifier of the unit. On an NFC item it comes from the identifier of the chip. On a QR-only item, the server draws it at random at mint time.
Token identifiera number written in decimalthe identifier of the item on the chain
Printed serial number12 characterswhat the QR code on the product carries, in the address /p/{serial}

You can write the serial number in uppercase or in lowercase. We fold the characters that look alike into a single form before the search, so an I or an L typed by hand finds the 1, and an O finds the 0.

This endpoint resolves only the items still in the brand's catalog. An item destroyed on the chain, replaced by a later version, or archived answers 404.

#Headers

No header is required.

#Request body

None. This request has no body.

#Example request

Proofs of the passport of the item whose token identifier is 1042.

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

#Example response

HTTP code 200.

JSON
{
  "passport_version": 3,
  "data_hash": "1111111111111111111111111111111111111111111111111111111111111111",
  "ipfs_uri": "ipfs://bafybeiexemple00000000000000000000000000000000000000000000",
  "ipfs_gateway_url": "https://ipfs.io/ipfs/bafybeiexemple00000000000000000000000000000000000000000000",
  "anchor": {
    "chain": "base",
    "chain_id": 8453,
    "type": "merkle_batch",
    "tx_hash": "0x2222222222222222222222222222222222222222222222222222222222222222",
    "basescan_url": "https://basescan.org/tx/0x2222222222222222222222222222222222222222222222222222222222222222",
    "merkle_root": "0x3333333333333333333333333333333333333333333333333333333333333333",
    "anchored": true,
    "proves": "batch_inclusion"
  },
  "passport_anchor": {
    "chain": "base",
    "chain_id": 8453,
    "tx_hash": "0x4444444444444444444444444444444444444444444444444444444444444444",
    "basescan_url": "https://basescan.org/tx/0x4444444444444444444444444444444444444444444444444444444444444444",
    "merkle_root": "0x5555555555555555555555555555555555555555555555555555555555555555",
    "leaf": "0x6666666666666666666666666666666666666666666666666666666666666666",
    "leaf_index": 2,
    "proof": [
      "0x7777777777777777777777777777777777777777777777777777777777777777",
      "0x8888888888888888888888888888888888888888888888888888888888888888"
    ],
    "anchored_at": "2026-08-14T02:15:09+00:00",
    "data_hash_matches": true,
    "proves": "content_existed_at_or_before_tx"
  },
  "seal": {
    "sealed": true,
    "sealed_at": "2026-08-13T09:41:22+00:00",
    "algorithm": "st-dpp-chain-v1",
    "version_hash": "9999999999999999999999999999999999999999999999999999999999999999",
    "prev_version_hash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    "linked": true,
    "chain_link_match": true
  },
  "vc": {
    "issued": true,
    "vct": "https://schema.sealtrust.io/vct/digital-product-passport",
    "issued_at": "2026-08-13T09:41:25+00:00"
  },
  "verifications": {
    "count": 47,
    "last_verified_at": "2026-08-19T18:03:44+00:00"
  }
}

passport_version, seal and vc are always present. We omit the other top-level fields when the matching proof does not exist. An absence means that this proof is not available. Treat it as information, in the same way as a value.

FieldTypePresenceDescription
passport_versionintegeralwaysThe version number of the published passport that every proof below relates to.
data_hashstringif recordedThe SHA-256 hash of the full content of the passport, 64 hexadecimal characters with no 0x prefix.
ipfs_uristringif the copy is verifiedThe IPFS address of the public copy of the passport.
ipfs_gateway_urlstringif the copy is verifiedThe same copy, as a web address you can open in a browser.
anchorobjectif the item has a reference on the chainThe reference on the chain that concerns the ITEM. See below.
passport_anchorobjectif the version was anchoredThe anchoring that dates the DOCUMENT. See below.
sealobjectalwaysThe seal of the version and its place in the sequence of versions. See below.
vcobjectalwaysThe state of the signed credential attached to the passport. See below.
verificationsobjectif the count is above zeroThe number of successful verifications recorded for this unit, by QR as well as by NFC, and the date of the last one. Also absent when the unit carries no identifier hash.

The passport served here covers either this precise unit or its model. We first look for a passport attached to the unit. Failing that, we serve the passport published for its model, shared by every unit that carries the same product code. In that second case, data_hash, ipfs_uri, ipfs_gateway_url, passport_anchor, seal and vc describe the document of the model. anchor and verifications remain specific to the unit you asked for.

The response also carries the header Cache-Control: no-store, max-age=0. 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 use tolerates.

#The anchor block, the reference of the item

This block is present when the item carries a reference on the chain. It covers two different situations, and it is the anchored field that separates them.

FieldTypeDescription
chainstringThe network, in machine readable form. base in production.
chain_idintegerThe identifier of the network. 8453 for Base in production.
typestringmerkle_batch or mint_transaction.
tx_hashstringThe transaction concerned.
basescan_urlstringThe direct link to this transaction on the public explorer of the network.
merkle_rootstringThe batch root recorded on the chain. Present only when type reads merkle_batch and the root is recorded.
anchoredbooleantrue only when the batch of the item was recorded on the chain.
provesstringbatch_inclusion when the batch is anchored, token_minted otherwise.

anchored: true means that the batch the item belongs to was recorded on the chain. You obtain the inclusion proof itself from GET /v1/verify/merkle/{identifier}, when this block also carries merkle_root. Without merkle_root, that call answers 404 and detail reads No Merkle anchor for this product.

anchored: false means that the batch of the item was never recorded. The block then carries the mint transaction of the item. It shows that the token exists on the chain. It shows nothing about a batch, and nothing about the content of the passport.

In both cases, this reference carries no commitment on the content of the passport. The commitment on the content is read in passport_anchor and in seal.

#The passport_anchor block, the date of the document

This block is present when this precise version of the passport was recorded on the chain. It establishes one single thing: the content existed at the latest at the moment of that transaction.

FieldTypeDescription
typestringAlways passport_anchor. It tells this anchoring apart from the one in the anchor block, which carries a batch of items.
anchoredbooleanAlways true. The block exists only once the version has been recorded on the chain.
chainstringThe network, in machine readable form.
chain_idinteger or nullThe identifier of the network. null on an anchoring that predates the recording of the network. chain then indicates the production network.
tx_hashstringThe transaction that carries the root.
basescan_urlstringThe direct link to this transaction on the public explorer of the network.
merkle_rootstringThe root recorded on 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 the recorded hashes. Counting starts at 0.
proofstring[]The neighboring hashes to combine with leaf, from the bottom up, to recover merkle_root. The list is empty when the recording covered only one version.
anchored_atstring or nullThe date of the recording, formatted as ISO 8601.
data_hash_matchesbooleantrue when the passport recorded today still matches what was recorded on the chain.
provesstringAlways content_existed_at_or_before_tx.

You can redo the inclusion check without trusting us. 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 verification convention of OpenZeppelin, with neighbors sorted at each level. Then open basescan_url to read the transaction that recorded this root.

The absence of passport_anchor is common and normal. Publishing a version does not record it on the chain. The recording is a separate operation, done in grouped passes, and many published passports never carry this block. So conclude nothing from its absence about the validity of the passport. The seal block, on the other hand, is always present and covers the versions that were not recorded.

#The seal block, the seal of the version

This block is always present. It holds on the versions that passport_anchor does not cover.

FieldTypePresenceDescription
sealedbooleanalwaysfalse when the version was never sealed. The block then stops there.
sealed_atstringif sealedThe date of the sealing, formatted as ISO 8601.
algorithmstringif sealedThe version of the chaining algorithm. Reads st-dpp-chain-v1.
version_hashstring or nullif sealedThe hash of this link, 64 hexadecimal characters.
prev_version_hashstring or nullif sealedThe hash of the previous link. null for the first published version.
linkedbooleanif sealedtrue when this version carries a link hash.
reasonstringif linked is falseReads sealed_before_chain. The version was published before chaining existed, so no link is manufactured after the fact. The block stops there.
chain_link_matchbooleanif linkedtrue when the passport recorded today still produces exactly the stored link.

chain_link_match is recomputed on every call from the recorded content. false means that the version was changed after it was published.

#The vc block, the signed credential

This block is always present.

FieldTypePresenceDescription
issuedbooleanalwaystrue when a signed credential was issued for this passport.
vctstringif recordedThe identifier of the credential template. Today it reads https://schema.sealtrust.io/vct/digital-product-passport.
issued_atstringif recordedThe date of issuance, formatted as ISO 8601.

#The IPFS copy

ipfs_uri and ipfs_gateway_url designate the public copy of the passport. The content at that address is the public view of the passport. Its hash therefore differs from data_hash, which covers the full content.

You receive these two fields only when we have fetched the copy and checked that it is exactly the public passport that the brand publishes today. Their absence can therefore mean three things: no copy was ever deposited, the copy could not be fetched at the moment of the 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 deposited before that change stops being announced until a new one has been deposited. Conclude nothing from their absence alone.

The host of ipfs_gateway_url is that of a public gateway and can change. Do not hard code it. Use the address returned as it stands, or ipfs_uri with the gateway of your choice.

#Errors

The body of an error response carries a detail field.

CodeConditionWhat to do
404No item in the catalog matches this identifier, under any of the three accepted forms. detail reads Product not found.Check the identifier. An item destroyed on the chain, replaced by a later version, or archived no longer resolves and gives this same response.
404The item exists, but no passport published with public visibility is attached to it, neither directly nor through its model. detail reads No published passport found for this product.Do not treat this response as a failure. This item has no public passport. A passport reserved for the owner or for the brand gives the same response.
429The limit of 60 calls per 60 seconds is reached for your network address, across all the /passport paths. detail reads Rate limit exceeded: 60 requests per 60s.Wait the number of seconds given by Retry-After, then retry. Cache the response on your side.
500An unexpected error occurred while processing your call. detail reads Internal Server Error. The response carries an X-Request-Id header.Retry. If the error persists, contact support with the value of X-Request-Id.

An anchoring read or an IPFS copy read that fails does not produce an error. The block concerned is absent, and the other proofs are served normally.

#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