# GET /p/{serial}

Translate the serial number printed on an item into the address of its consumer page. Public endpoint, no API key, which answers with a redirect.

Source: https://docs.sealtrust.io/en/reference/get-p-serial/

---

You turn an item's serial number into the address of the page that presents it
to a consumer, through a redirect.

## Authorization

None, public endpoint. We expect neither an API key, nor a session cookie, nor
an `Authorization` header. You can call this address from a server.

## Rate limit

This address has no limit of its own. It falls under the general API counter,
counted per calling IP address over a 60-second window, and shared with all the
other addresses that have no limit of their own.

Plan for the 429 code in your client and honor the `Retry-After` header it
carries. The value of this counter may change without notice, so hard-code no
number in your code.

The response carries the headers `X-RateLimit-Limit`, `X-RateLimit-Remaining`
and `X-RateLimit-Reset`, the last one giving the reset time in seconds since
January 1, 1970. Do not make your client depend on their presence: treat a
response that does not carry them as a normal response, and base your call rate
on the values you receive.

This address consumes no quota from your plan.

## Path and query parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `serial` | `string` | yes | The item's public serial number, 12 characters. Path parameter. |
| `linkType` | `string` | no | Asks for the passport rather than the consumer page. Five values trigger it: `dpp`, `passport`, `gs1:dpp`, `gs1:digitalproductpassport`, `digitalproductpassport`. Case and edge spaces do not matter. We treat any other value as if the parameter were absent. We also accept the spelling `linktype`, all in lowercase. |

This serial number is the unique product identifier within the meaning of the
EN 18219 standard, the one the European digital product passport registry
expects, and the one encoded by the QR code printed on your items. The European
registry accepts no registration today, from anyone. We will submit this
identifier as soon as its registration opens.

This endpoint addresses a physical unit. A passport does not have to cover a
unit: the ESPR regulation provides for three levels, the model, the batch and
the item. Today we serve the model level and the item level. For a model
passport, the identifier to use is
[`GET /01/{gtin}`](/en/reference/get-gs1-gtin/), which resolves the reference
passport shared by all the units that carry the same product code. So you do not
have to number every unit in order to publish a passport.

The serial number is written in a 32-character alphabet: the digits `0` through
`9` and the letters `A` through `Z`, except `I`, `L`, `O` and `U`. Those four
letters are set aside because they get confused with digits on a label.

Before any lookup, we clean up the value received: we strip edge spaces, we
replace `I` and `L` with `1`, `O` with `0`, and we put the whole thing in
uppercase. So someone retyping a number by hand can get `I`, `L` and `O` wrong
with no consequence. We understand `ilo2345678ab` as `1102345678AB`, and it is
that corrected form that appears in the destination address. The letter `U` is
not corrected: a value that contains one goes out as a 404.

After this cleanup, we refuse with a 404 any value that is not exactly 12
characters, or that contains a character outside the alphabet. The
authentication parameters that some NFC chips add to the address at scan time
are query parameters: we do not read them here and they change nothing in the
response.

> [!ATTENTION] The serial number is not the token identifier
> The number printed on the label and the token identifier on the chain are two
> different values. This endpoint accepts only the serial number. The token
> identifier is 77 to 78 digits long and does not fit into an identifier
> address.

## Request body

None. This is a `GET` request: everything goes through the path and the query
parameters.

## Example request

Do not follow the redirect. What you want to read is the `Location` header.

### Two addresses for the same route

For a server call, call `https://api.sealtrust.io/v1/p/{serial}`. The same
endpoint also answers without the `/v1` prefix. For a new server integration,
use the `/v1` form.

The QR code we generate for your items, for its part, carries the form without a
prefix, on the domain name of your consumer pages, for example
`https://sealtrust.io/p/{serial}`. That is the form that gets printed and that
serves as the identifier, because an identifier printed on a label can no longer
be corrected.

:::onglets
```bash title="curl"
curl -i https://api.sealtrust.io/v1/p/000000000000 \
  -H "Accept-Language: fr"
```
```typescript
const response = await fetch("https://api.sealtrust.io/v1/p/000000000000", {
  headers: { "Accept-Language": "fr" },
  redirect: "manual",
});

console.log(response.status);
console.log(response.headers.get("Location"));
```
```python
import requests

response = requests.get(
    "https://api.sealtrust.io/v1/p/000000000000",
    headers={"Accept-Language": "fr"},
    allow_redirects=False,
    timeout=30,
)

print(response.status_code)
print(response.headers["Location"])
```
:::

> [!ATTENTION] The TypeScript example is a server example
> Run in a browser, `redirect: "manual"` hides the status code and the
> `Location` header. Make this call from your server.

> [!INFO] The TypeScript SDK does not cover this endpoint
> The `@sealtrust-io/sdk` package has no identifier resolution method. So you
> call this address over plain HTTP, as above. The three tabs show the same
> operation with the same values.

## Example response

HTTP code 302. The body is empty, all the information is in the `Location`
header.

```http
HTTP/1.1 302 Found
Location: https://sealtrust.io/fr/product/000000000000
```

With `Accept-Language: en-GB,en;q=0.9`, the same request returns the English
version of the page.

```http
HTTP/1.1 302 Found
Location: https://sealtrust.io/en/product/000000000000
```

With `?linkType=dpp`, the destination becomes the item's passport.

```http
HTTP/1.1 302 Found
Location: https://sealtrust.io/fr/passport/0x0000000000000000000000000000000000000000000000000000000000000000
```

### How we choose the destination

This endpoint returns no authenticity verdict. We find the item that carries
this serial number, then we return the address of the page to display. For a
verdict, call [`GET /qr/verify`](/en/reference/get-qr-verify/) with the signed
parameters of the QR code.

| Case | Destination |
| --- | --- |
| Item in the catalog, with no recognized `linkType` | The item's consumer page, addressed by its corrected serial number. |
| Recognized `linkType` | The item's passport, addressed by its hash. If the item has no hash, by its token identifier, and failing that by its serial number. |
| Item withdrawn from the catalog | The passport, with or without `linkType`. An item replaced by a more recent mint or archived therefore remains resolvable, which the EN 18219 standard requires for a withdrawn identifier. |

> [!INFO] Exactly one redirect
> We return a destination already prefixed with the language, `/fr/` or `/en/`.
> The European registry fetches identifier addresses in order to validate them
> and penalizes redirect chains, so we aim straight at the final address.

The destination's language comes from the `Accept-Language` header. We take the
first declared language that is French or English. With no header, or with a
language we do not serve, we answer with a destination in French.

If you serve your consumer pages on your own domain name, we build the
destination on that domain name, over `https`, and the visitor therefore does
not leave your domain.

When several records carry the same serial number, because you have re-minted an
item, we answer for the record that is still in the catalog. If there is none,
we answer for the most recent one.

## Errors

| Code | Condition | What to do |
| --- | --- | --- |
| 400 | The call arrives on a host name we do not serve, for example a brand domain whose verification is no longer valid. The response carries no JSON body. | Call `api.sealtrust.io`, or have your domain name verified again in the console. |
| 404 | The serial number is malformed: length other than 12 after cleanup, or a character outside the alphabet. Message `Unknown product identifier`. | Check the value you copied. Add nothing to the number in the path, every extra parameter goes in the query string. |
| 404 | No item carries this serial number. Message `Unknown product identifier`. | Nothing to fix in the call. The number was never assigned, or it belongs to another system. |
| 404 | The call arrives on a brand's domain name and the item belongs to another brand. Message `Unknown product identifier`. | Call this serial number on the domain of the brand it belongs to, or on `api.sealtrust.io`. |
| 429 | More than 60 calls in 60 seconds from the same IP address, across every `/p/` path. Message `Rate limit exceeded: 60 requests per 60s`. The response carries the `Retry-After` header, in seconds. | Wait the number of seconds indicated by `Retry-After`. Spread out your calls instead of sending them in bursts. |
| 500 | Unexpected error on our side. Fixed body `{"detail": "Internal Server Error"}`. | Try again. The `X-Request-Id` header identifies the call, pass it on to us if it happens repeatedly. |

> [!INFO] The three 404s are indistinguishable
> The three conditions above return exactly the same code and the same message.
> That is deliberate: a different response depending on the case would tell a
> visitor which serial numbers exist, and would let them enumerate them. So a
> 404 never tells you which of the three situations you have run into.

## See also

- [`GET /01/{gtin}/21/{serial}`](/en/reference/get-gs1-gtin-serial/),
  resolve a GS1 link that carries a GTIN and a serial number.
- [`GET /01/{gtin}`](/en/reference/get-gs1-gtin/),
  resolve a GS1 link that carries only a GTIN.
- [`GET /qr/verify`](/en/reference/get-qr-verify/),
  verify an item from a verification address of the older
  form.
- [`GET /resolve/{identifier}`](/en/reference/get-resolve/),
  read in a single call everything a product page displays.
- [Physical identification, QR and NFC](/en/identification-physique/),
  choose the physical carrier and the exact form of the GS1 Digital Link.
