Method GET/passport/{identifier}/verify

Check the integrity of the published passport of an item: recorded hash, immutable public copy and version seal. Public endpoint.

On this page

You check that a published passport has not been modified since its publication. By the end of this page, you will know how to ask for this check from any item identifier, how to read the three verdicts the response returns, and how to tell an altered passport from a check that could not complete.

Full address:

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

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

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

#Rate limit

60 calls per 60-second window, counted by calling network address.

This counter is shared by every path that starts with /passport. The calls you send to one of them therefore eat into the budget of the others. The /v1 prefix does not create a second budget: /v1/passport/0ABCDEFGHJKM/verify and /passport/0ABCDEFGHJKM/verify fill the same counter.

Plan for a timeout of at least 30 seconds on the client side. During your call, this endpoint goes to fetch the immutable public copy from public gateways. It queries several of them one after the other, each with its own time limit, so a first call on a copy the server has never read yet can last some twenty seconds. The calls that follow on the same copy answer without going to fetch it again. When no gateway answers, the response stays 200 and ipfs_match is null.

Every accepted response carries three headers.

HeaderContent
X-RateLimit-Limitthe cap applied over the window, here 60
X-RateLimit-Remainingwhat is left to you in the current window
X-RateLimit-Resetthe end-of-window timestamp, in seconds

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

#Path and query parameters

NameTypeRequiredDescription
identifierstringyesThe item whose passport you want to check. Three forms are accepted, see below.

This endpoint has no query parameter.

identifier accepts three forms, tried in this order.

FormWhat it looks likeWhere it comes from
Item hash0x followed by 64 hexadecimal charactersFor an NFC item, the hash of the identifier read on the chip. For a QR item, a hash the server draws at random at the moment of minting. Both have the same form and you use them the same way.
Token identifiera 256-bit integer written in decimal, 77 or 78 digitsThe identifier of the item on the chain. Treat it as a string of characters: it exceeds a 64-bit integer.
Printed serial number12 charactersWhat the QR code carries on the product, in the /p/{serial} address.

The server recognizes the item hash without distinction of case. It recognizes the serial number the same way, and it brings the characters that look alike back to a single form before searching: an I or an L that you type by hand finds the 1 again, an O finds the 0.

This endpoint does not accept the certificate of authenticity number. You use it on GET /certificate/{identifier}.

#Headers

No header is required.

#Request body

None. This request has no body.

#Example request

Integrity check of the passport of the item whose printed serial number is 0ABCDEFGHJKM. The examples use this form because it fits in 12 characters. A token identifier is written at the same place in the address, on 77 or 78 digits. The three examples set the same timeout of 30 seconds, for the reason given above.

curl -i --max-time 30 https://api.sealtrust.io/v1/passport/0ABCDEFGHJKM/verify

#Example response

HTTP code 200.

JSON
{
  "db_hash_match": true,
  "ipfs_match": true,
  "ipfs_uri": "ipfs://bafybeiaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
  "ipfs_gateway_url": "https://passerelle.exemple.invalid/ipfs/bafybeiaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
  "data_hash": "1111111111111111111111111111111111111111111111111111111111111111",
  "computed_hash": "1111111111111111111111111111111111111111111111111111111111111111",
  "passport_version": 3,
  "seal": {
    "sealed": true,
    "sealed_at": "2026-05-14T09:12:44+00:00",
    "algorithm": "st-dpp-chain-v1",
    "version_hash": "2222222222222222222222222222222222222222222222222222222222222222",
    "prev_version_hash": "3333333333333333333333333333333333333333333333333333333333333333",
    "linked": true,
    "chain_link_match": true
  }
}

The response has eight fields and nothing else.

FieldTypeDescription
db_hash_matchboolean or nulltrue when the recorded data of the passport still matches the hash recorded with it. null when no hash was recorded for this version.
ipfs_matchboolean or nulltrue when the immutable public copy matches the public version of the passport. false when it differs from it. null when no copy exists, or when no gateway answered.
ipfs_uristring or nullThe ipfs:// address of the immutable public copy. Filled in only when that copy is the public passport the brand publishes today, see below.
ipfs_gateway_urlstring or nullThe same copy, as an HTTP address that opens in a browser. Filled in under the same conditions as ipfs_uri.
data_hashstring or nullThe hash recorded with this version of the passport, 64 hexadecimal characters. null when no hash was recorded.
computed_hashstringThe hash recomputed at the moment of your call from the recorded data, 64 hexadecimal characters. It covers the complete data of the passport, including the fields this endpoint does not return and that the public version of the passport does not return either. You therefore cannot reproduce it yourself from public data.
passport_versionintegerThe version number of the passport checked. It starts at 1 and increases by one at every new version of the passport.
sealobjectThe seal of this version and its place in the chain of versions, see below.

#Which passport the server checks

The check covers the latest published version whose visibility is public. The server first looks for the passport specific to the item. Failing that, it checks the passport of the model, shared by all the items of the model.

The response does not say which of the two scopes answered. Both number their versions separately, so passport_version can be 1 in either case.

The server never checks here a passport reserved to the owner or a passport reserved to the brand. The response is then 404, as if no passport were published.

#When the server returns the ipfs_uri address to you

You receive the address of the public copy when its content is exactly the public passport the brand publishes today. Otherwise, ipfs_uri and ipfs_gateway_url are null.

A brand that changes its access rules changes what its public passport shows. A copy posted before that change is therefore no longer announced until the brand has posted a new one. Never read these two null values as a verdict on the copy.

ipfs_match at null says nothing about the integrity of the copy. Try again later before concluding.

#The seal block

FieldTypeDescription
sealedbooleanfalse when the version is not sealed. The block then stops there and carries no other field.
sealed_atstringThe date and time of the sealing, in ISO 8601 format.
algorithmstringThe version of the chaining algorithm. Today it is st-dpp-chain-v1.
version_hashstring or nullThe sealed hash of this version, 64 hexadecimal characters.
prev_version_hashstring or nullThe sealed hash of the previous version of the same passport. null for the very first version.
linkedbooleantrue when this version carries a sealed hash and therefore takes its place in the chain.
reasonstringPresent only when linked is false. It is then sealed_before_chain: you published this version before the chaining existed, and the server manufactures none after the fact.
chain_link_matchbooleanPresent only when linked is true. false means that the recorded data no longer matches what was sealed.

The seal block therefore takes three forms, and you must be able to tell them apart without deducing anything from a missing field.

JSON
{ "sealed": false }
JSON
{
  "sealed": true,
  "sealed_at": "2025-11-02T08:30:00+00:00",
  "algorithm": "st-dpp-chain-v1",
  "version_hash": null,
  "prev_version_hash": null,
  "linked": false,
  "reason": "sealed_before_chain"
}
JSON
{
  "sealed": true,
  "sealed_at": "2026-05-14T09:12:44+00:00",
  "algorithm": "st-dpp-chain-v1",
  "version_hash": "2222222222222222222222222222222222222222222222222222222222222222",
  "prev_version_hash": "3333333333333333333333333333333333333333333333333333333333333333",
  "linked": true,
  "chain_link_match": true
}

#Which verdict to read first

chain_link_match at false is the signal that a sealed version was modified after its publication. It is the strongest verdict in this response. It comes from a recomputation made at the moment of your call.

ipfs_match at false carries the same kind of signal about the public copy.

#Errors

The body of an error response carries a detail field.

CodeConditionWhat to do
404No item matches this identifier, under any of the three accepted forms. detail is 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 public passport is published for it or for its model. detail is No published passport found for this product.Do not treat this response as a failure of the check. There is nothing to check. A passport reserved to the owner or to the brand also gives this response.
429The cap of 60 calls per 60 seconds is reached for your network address, across all paths starting with /passport. detail is Rate limit exceeded: 60 requests per 60s.Wait the number of seconds given by Retry-After, then try again.
500An unexpected error occurred during the processing of your call. 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.

An unreachable gateway does not produce an HTTP error. The response stays 200 and ipfs_match is null.

#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