# GET /products/{uid}/public

Read the public information of a product from its identifier: name, brand, image, token, transaction, state and theft report. No API key.

Source: https://docs.sealtrust.io/en/reference/get-products-uid-public/

---

You read the public information of a product from its identifier. By the end of
this page, you will know how to retrieve its name, the name and logo of its
brand, an image, its token number, its registration transaction, its state, and
whether it is reported stolen.

Full address:

```http
GET https://api.sealtrust.io/v1/products/{uid}/public
```

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

## Authorization

None, public endpoint. This endpoint reads no API key, no token and no cookie.
The server never looks at the `Authorization` header you send here, and it
returns the same response to you with or without it.

> [!ATTENTION] Everything this endpoint returns is public
> The identifier requested in the path travels outside your control. It is
> encoded in the sharing QR of a product, which opens the public page
> `/verify/{uid}`, and it appears in the clear in the address of that page.
> Anyone who has received that QR or that address can therefore call this
> endpoint and read these fields. Expect no confidentiality from it, and never
> use knowledge of the identifier as proof of possession.

## Call limit

No limit of its own for this endpoint. This path falls under the general counter
of the API, counted per calling IP address over a 60 second window. That counter
is shared with all the other API paths that have no limit of their own.

Plan for code 429 in your client and honor the `Retry-After` header it carries.
The value of that counter can change without notice, so do not write any number
into your code.

The response normally carries three headers that describe this counter. Build
nothing that requires their presence: they can be missing.

| Header | Content |
| --- | --- |
| `X-RateLimit-Limit` | the ceiling applied over the window |
| `X-RateLimit-Remaining` | what is left to you in the current window |
| `X-RateLimit-Reset` | the timestamp of the end of the window, in seconds |

That counter is independent of the daily quota of an API key. This call eats
into neither that daily quota, nor the monthly product quota of your plan.

## Path and query parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `uid` | `string` | yes | The public identifier of the product: `0x` followed by 64 hexadecimal characters. It is the hash of the product's chip. |

> [!ATTENTION] This identifier is not the serial number on the label
> The QR code printed on the item encodes the serial number and opens
> `/p/{serial}`. You read that serial number with `GET /p/{serial}`. The
> identifier expected here is another value. It appears in the sharing QR
> generated from the page of a product, which opens `/verify/{uid}`. Sending a
> serial number to this address returns you a 404.

You can send this identifier with or without the `0x` prefix, in uppercase or in
lowercase. The server strips the leading and trailing spaces before searching,
puts the value in lowercase and adds the `0x` prefix if it is missing. So these
spellings all designate the same product.

This endpoint has no query parameter.

### Headers

No header is required.

## Request body

None. This request has no body.

## Example request

Reading the product whose identifier is
`0x1111111111111111111111111111111111111111111111111111111111111111`.

:::onglets
```bash title="curl"
curl -i "https://api.sealtrust.io/v1/products/0x1111111111111111111111111111111111111111111111111111111111111111/public"
```
```typescript
const uid =
  "0x1111111111111111111111111111111111111111111111111111111111111111";

const response = await fetch(
  `https://api.sealtrust.io/v1/products/${encodeURIComponent(uid)}/public`,
  { method: "GET" },
);

if (!response.ok) {
  throw new Error(`SealTrust a répondu ${response.status}`);
}

const produit = await response.json();

console.log(produit.product_name, produit.brand_name);
console.log(produit.is_verified, produit.declared_stolen);
```
```python
import requests
from urllib.parse import quote

uid = "0x1111111111111111111111111111111111111111111111111111111111111111"

response = requests.get(
    f"https://api.sealtrust.io/v1/products/{quote(uid, safe='')}/public",
    timeout=30,
)

print(response.status_code)
print(response.json())
```
:::

> [!INFO] The TypeScript SDK does not cover this endpoint
> `@sealtrust-io/sdk` exposes no method for this address. The example above uses
> `fetch`, available with no dependency.

## Example response

HTTP code `200`.

```json
{
  "product_name": "Sac Modèle Exemple",
  "brand_name": "Exemple SAS",
  "brand_logo_url": "https://exemple-sas.test/logo.png",
  "image_url": "https://exemple-sas.test/media/sac-modele-exemple.jpg",
  "category_name": "Maroquinerie",
  "token_id": "11111111111111111111111111111111111111111111111111111111111111111111111111111",
  "tx_hash": "0x2222222222222222222222222222222222222222222222222222222222222222",
  "status": "written",
  "declared_stolen": false,
  "is_verified": true,
  "verified_at": "2026-03-04T10:22:31.481000+00:00",
  "contract_address": "0x3333333333333333333333333333333333333333",
  "chain": null
}
```

The response has thirteen fields and nothing else.

| Field | Type | Description |
| --- | --- | --- |
| `product_name` | `string \| null` | The name of the product. `null` when no name has been recorded. |
| `brand_name` | `string \| null` | The name of the brand that owns the product. `null` when the product is attached to no brand. |
| `brand_logo_url` | `string \| null` | The address of the brand's logo. `null` when the brand has not uploaded one. |
| `image_url` | `string \| null` | The public address of the cover image. It is looked for in this order: an image attached directly to the item, then the main image of its model, then the media of the model. `null` only when none of these three tracks succeeds. |
| `category_name` | `string \| null` | The name of the product's category. `null` when no category is attached. |
| `token_id` | `string \| null` | The number of the token on the chain, written in base ten, in a character string. That number is too large for an ordinary integer, so read it as text and never as a number of your language. `null` as long as the registration on the chain is not confirmed. |
| `tx_hash` | `string \| null` | The hash of the transaction that registered the product on the chain. `null` as long as no transaction has been recorded. |
| `status` | `string` | The technical state of the product. See below. |
| `declared_stolen` | `boolean` | `true` when a theft report is open on this product. |
| `is_verified` | `boolean` | `true` when `status` is `mined` or `written`. `false` in every other case. |
| `verified_at` | `string \| null` | Date and time of creation of the product's record, in ISO 8601. See the warning below. |
| `contract_address` | `string` | The address of the contract that carries the token. |
| `chain` | `null` | This field is always `null` today. See below. |

### Reading the state of a product

`status` carries a technical value. This page documents two of them, because the
rest of the response depends on them: `mined` and `written` are the only two
values for which `is_verified` is `true`.

Read `is_verified` and `declared_stolen` rather than interpreting `status`
yourself. `declared_stolen` exists precisely for that: `status` carries the
value `stolen` at the moment of a fresh report, then takes the value `written`
back as soon as the theft case is closed, while `declared_stolen` says in a
field of its own whether a report is open.

> [!ATTENTION] `verified_at` is not a verification date
> Despite its name, this field carries the date of creation of the product's
> record on our side. It does not move when someone scans the product, and it
> says nothing about the last verification. Never display this value as
> "verified on".

> [!ATTENTION] `chain` is always empty
> This field is present in the response and is `null` for every product. Build
> nothing on it, and do not look there for the name of the network. Read
> `contract_address` to know the contract that carries the token.

## Errors

The body of an error response carries a `detail` field.

| Code | Condition | What to do |
| --- | --- | --- |
| 404 | No product carries this identifier. `detail` is `Product not found`. | The server first looks for your cleaned identifier, put in lowercase and prefixed with `0x`, then for your raw string as you sent it. Send the canonical form: `0x` followed by 64 lowercase hexadecimal characters. Also check that you really are sending a chip hash and not a serial number. |
| 429 | Too many calls from your address. The response carries the `Retry-After` header, in seconds. | Wait the number of seconds indicated by `Retry-After`, then try again. Spread out your calls instead of sending them in bursts. |
| 500 | An unexpected error occurred while handling your call. `detail` is `Internal Server Error`. The response carries an `X-Request-Id` header. | Try again. If the error persists, contact support giving this request identifier and the time of the call. |

This endpoint is public: it returns neither 401 nor 403.

## See also

- [`GET /resolve/{identifier}`](/en/reference/get-resolve/),
  read in a single call everything a product page displays.
- [`GET /timeline/{identifier}`](/en/reference/get-timeline/),
  read the public history of a product.
- [`GET /certificate/{identifier}`](/en/reference/get-certificate/),
  read the certificate of authenticity of an item.
- [Core concepts](/en/notions/),
  tell model, batch and item apart before ordering a single label.
