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:
GET https://api.sealtrust.io/v1/passport/01/{gtin}/proofThe 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.
#
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.
| Header | Content |
|---|---|
X-RateLimit-Limit | the limit applied over the window, here 60 |
X-RateLimit-Remaining | what is left to you in the current window |
X-RateLimit-Reset | the 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
| Name | Type | Required | Description |
|---|---|---|---|
gtin | string | yes | The 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/proofconst reponse = await fetch(
"https://api.sealtrust.io/v1/passport/01/03701234567890/proof",
);
if (reponse.status === 404) {
console.log("Aucun passeport de référence publié pour ce GTIN.");
} else if (reponse.ok) {
const preuves = await reponse.json();
console.log(preuves.gtin, preuves.level, preuves.passport_version);
console.log(preuves.data_hash);
console.log(preuves.ipfs_gateway_url);
console.log(preuves.passport_anchor);
console.log(preuves.seal);
console.log(preuves.vc);
} else {
console.log(reponse.status, await reponse.json());
}import requests
response = requests.get(
"https://api.sealtrust.io/v1/passport/01/03701234567890/proof",
timeout=30,
)
if response.status_code == 404:
print("Aucun passeport de référence publié pour ce GTIN.")
elif response.ok:
preuves = response.json()
print(preuves["gtin"], preuves["level"], preuves["passport_version"])
print(preuves.get("data_hash"))
print(preuves.get("ipfs_gateway_url"))
print(preuves.get("passport_anchor"))
print(preuves["seal"])
print(preuves["vc"])
else:
print(response.status_code, response.json())#Example response
HTTP code 200.
{
"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.
| Field | Type | Description |
|---|---|---|
passport_version | integer | The 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_hash | string | The SHA-256 hash of the content of this version, 64 hexadecimal characters with no 0x prefix. Absent when the version carries none. |
ipfs_uri | string | The IPFS address of the frozen public copy of this passport. See below the condition that governs its presence. |
ipfs_gateway_url | string | The 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_anchor | object | The 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. |
seal | object | The seal of the version and its place in the sequence of versions. Always present. See below. |
vc | object | The status of the signed credential of the passport. Always present. See below. |
level | string | Always model. A reminder that these proofs bear on a model. |
gtin | string | The 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.
| Field | Type | Description |
|---|---|---|
chain | string | The network, base in production. |
chain_id | integer or null | The 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_hash | string | The transaction that carries the write of the root. |
basescan_url | string | The direct link to that transaction on the public explorer of the network. |
merkle_root | string | The root written to the chain, 0x followed by 64 hexadecimal characters. |
leaf | string | The hash of this version in the tree, 0x followed by 64 hexadecimal characters. |
leaf_index | integer | The position of this hash in the list of hashes written together. Counting starts at 0. |
proof | string[] | The neighboring hashes to combine with leaf to obtain merkle_root. The list is empty when the write covered only one version. |
anchored_at | string or null | The moment of the write, in ISO 8601 format. null when that moment was not recorded. |
data_hash_matches | boolean | true 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. |
proves | string | Always 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.
| Field | Type | Description |
|---|---|---|
sealed | boolean | false for a version that is not sealed. The block then contains nothing else. |
sealed_at | string | The moment of sealing, in ISO 8601 format. |
algorithm | string | The version of the seal computation, st-dpp-chain-v1 today. |
version_hash | string or null | The chain link of this version, 64 hexadecimal characters. |
prev_version_hash | string or null | The chain link of the previous version. null for the first version of a passport. |
linked | boolean | true when the version carries a chain link. |
reason | string | Present 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_match | boolean | Present 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.
| Field | Type | Description |
|---|---|---|
issued | boolean | true when a signed credential exists for this version. Always present. |
vct | string | The identifier of the credential schema, https://schema.sealtrust.io/vct/digital-product-passport by default. Absent when the version carries none. |
issued_at | string | The 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.
| Code | Condition | What to do |
|---|---|---|
| 400 | The 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. |
| 404 | The 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. |
| 404 | No registered model carries this GTIN. detail is Unknown GS1 Digital Link. | Check the GTIN with the brand. |
| 404 | A 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. |
| 429 | The 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. |
| 500 | An 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
GET /passport/01/{gtin}, read the published passport of a model, from its GTIN.GET /passport/{identifier}/proof, gather the public proofs of the passport of an item.GET /01/{gtin}, resolve a GS1 link that carries only a GTIN.- Trust and proofs, what each proof establishes and how a third party redoes the verification.
Was this page helpful?
Your answer opens a pre-filled email in your mail app, addressed to contact@sealtrust.io. You read it over before sending it.