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

Source: https://docs.sealtrust.io/en/reference/get-passport-gtin-proof/

---

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.

> [!INFO] This endpoint bears on a model
> A GTIN identifies a commercial reference. It designates no physical item. This
> endpoint therefore serves the proofs of the reference-level passport, the one
> attached to a model and to no item. Two blocks present on the per-item version
> are absent here, and their absence is the right answer: `anchor`, which dates
> an item on the chain, and `verifications`, which counts the verifications of a
> physical label. A model has neither an item nor a label.

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

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

> [!INFO] The TypeScript SDK does not cover this endpoint
> No method of the `@sealtrust-io/sdk` package calls this address. The
> TypeScript example below uses `fetch`, with no dependency.

:::onglets
```bash title="curl"
curl -i https://api.sealtrust.io/v1/passport/01/03701234567890/proof
```
```typescript
const 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());
}
```
```python
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`.

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

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

> [!ATTENTION] The link between `leaf` and `data_hash` cannot be recomputed from this response alone
> The computation of `leaf` involves, in addition to `passport_version` and
> `data_hash`, an internal passport identifier that does not appear in this
> response. The `data_hash_matches` field is the result of that comparison, made
> by us. The chain that runs from `leaf` to `merkle_root`, for its part, stays
> verifiable by your own means.

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}`](/en/reference/get-passport-gtin/),
  read the published passport of a model, from its GTIN.
- [`GET /passport/{identifier}/proof`](/en/reference/get-passport-proof/),
  gather the public proofs of the passport of an item.
- [`GET /01/{gtin}`](/en/reference/get-gs1-gtin/),
  resolve a GS1 link that carries only a GTIN.
- [Trust and proofs](/en/confiance-et-preuves/),
  what each proof establishes and how a third party redoes the verification.
