Method GET/01 /{gtin} /21 /{serial}
Resolve a GS1 Digital Link (AI 01 + AI 21) to the item's public page. Public endpoint, no API key, which answers with a redirect.
On this page
You turn a GS1 Digital Link into the address of the item's public page. The
response carries the code 302 and the destination address in the Location
header. The response body is empty.
#
None, public endpoint. It expects neither an API key, nor a session cookie, nor
an Authorization header. A server-to-server call is accepted.
Call this endpoint on https://api.sealtrust.io. The same route also answers
under the /v1 prefix, at
https://api.sealtrust.io/v1/01/{gtin}/21/{serial}. Both addresses call the
same code. For a new server integration, use the /v1 form. The form without a
prefix exists because it is the /01/…/21/… path structure that makes an
address a GS1 Digital Link, so that is the one a code scanner encounters.
The domain we write into the GS1 links we produce is configurable, and defaults
to https://id.gs1.org. The identifier we declare to the European digital
product passport registry is the short address /p/{serial}, because a full GS1
link exceeds the registry's 50-character limit. The QR code we print on an item
also encodes /p/{serial}.
A GS1 link may also carry only the GTIN, without AI 21. It then designates the
commercial reference and resolves to the model's passport, shared by all the
units that carry that same product code. See GET /01/{gtin}. The ESPR
regulation allows a passport at the model, batch or unit level. We serve the
model level and the unit level. Nothing requires you to serialize every unit.
#Rate limit
This path has no limit of its own. It falls under the general API counter, counted per calling IP address over a 60-second window. That counter is shared by all the routes that have no limit of their own, and it is shared by all values of GTIN and serial number. Browsing a thousand different links consumes a thousand calls from the same budget.
Every 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.
This endpoint consumes no quota from your plan.
#Path and query parameters
| Name | Type | Required | Description |
|---|---|---|---|
gtin | string | yes | The GTIN of the commercial reference. We strip every character that is not a digit, including dashes and spaces, then we pad on the left with zeros up to 14 digits. A GTIN with no digit at all, or with more than 14 digits, gives 400. We also check its check digit: see below. |
serial | string | yes | The unit's public serial number: 12 characters from the Crockford Base32 alphabet, which excludes the letters I, L, O and U. We strip edge spaces, and case does not matter. Any other length, or any character outside that alphabet, gives 404. |
linkType | string | no | Asks for the passport rather than the product page. Five values trigger it, listed below. Case and edge spaces do not matter. |
We map the letters I and L to the digit 1, and the letter O to the digit 0, before looking for the unit. So you get the right answer even for a number retyped by hand with a lookalike character.
We ignore every other query parameter. An address that drags along the parameters of an NFC scan, or the campaign markers of a shared link, resolves exactly like the bare address.
#The five values of linkType
| Value | Effect |
|---|---|
dpp | Redirects to the unit's passport. |
passport | Same. |
gs1:dpp | Same. |
gs1:digitalproductpassport | Same. |
digitalproductpassport | Same. |
Any other value gives the same response as a missing parameter, so the redirect
goes to the product page. You can write the parameter name linkType or
linktype.
#The GTIN check digit
The last digit of a GTIN is its check digit: the GS1 modulo 10 rule computes it from the digits that precede it. We recompute it and compare before any lookup.
So count 8, 12, 13 or 14 digits, the four lengths a GTIN can have, and check
that the last one really is the check digit of the preceding ones. A GTIN that
falls outside that rule receives a 400 code, with the body
{"detail": "Invalid GTIN: the check digit does not match."}.
Copy the GTIN from the reference's barcode. A typo on a single digit is then visible as soon as you make the call.
#What the GTIN has to satisfy
We compare the path's GTIN to the GTIN of the unit's model. The two must be identical. You draw two direct consequences from this.
A unit whose model carries no GTIN is not addressable in this form. It remains
addressable by its short address /p/{serial}.
A real serial number paired with a GTIN that is not its own answers 404, with exactly the same body as an unknown serial number. You cannot tell the two cases apart, and that is deliberate: the response never confirms that a serial number exists.
#A unit withdrawn from the catalog keeps answering
A unit replaced by a later mint and an archived unit remain resolvable. We redirect them to their passport rather than to the product page, because the product page no longer displays them. A code printed on an object that is still in someone's hands must not answer "unknown".
A serial number designates a single unit. When that unit has been replaced by a later mint, it is the current version that answers.
#Request body
None. This is a GET request, everything goes through the path.
#Example request
The three examples resolve the same link and do not follow the redirect, so that
you can see the Location header. They call the /v1 form, the one we
recommend for a server integration. Remove /v1 to call the address as it
appears in a GS1 link: the response is identical.
The TypeScript example runs server-side, under Node 18 or later.
curl -i https://api.sealtrust.io/v1/01/03701234567891/21/0000000000ABconst gtin = "03701234567891";
const serial = "0000000000AB";
const response = await fetch(
`https://api.sealtrust.io/v1/01/${gtin}/21/${serial}`,
{ redirect: "manual" },
);
console.log(response.status);
console.log(response.headers.get("Location"));
console.log(response.headers.get("X-RateLimit-Remaining"));import requests
gtin = "03701234567891"
serial = "0000000000AB"
response = requests.get(
f"https://api.sealtrust.io/v1/01/{gtin}/21/{serial}",
allow_redirects=False,
timeout=30,
)
print(response.status_code)
print(response.headers["Location"])
print(response.headers["X-RateLimit-Remaining"])#Example response
HTTP code 302. The body is empty. Everything is in the Location header.
HTTP/1.1 302 Found
Location: https://sealtrust.io/fr/product/0000000000AB
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 599
X-RateLimit-Reset: 1786000020
Content-Length: 0With ?linkType=dpp, or for a unit withdrawn from the catalog, the destination
is the passport.
HTTP/1.1 302 Found
Location: https://sealtrust.io/fr/passport/0x0000000000000000000000000000000000000000000000000000000000000000
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 598
X-RateLimit-Reset: 1786000020
Content-Length: 0#What makes up the destination address
| Element | How we choose it |
|---|---|
| The host | The public SealTrust site. If your request arrived on the verified domain name of the owning brand, we keep the redirect on that domain, over https. |
| The language | fr or en. We read your Accept-Language header and we keep the first language in your list that is one of the two. If none matches, we answer fr. |
| The path | product/{serial} by default. passport/{identifier} when you ask for the passport, or when the unit is withdrawn from the catalog. |
{identifier} is the unit's public hash, a 0x string followed by 64
hexadecimal characters. For a unit that has none, it is its token identifier,
and failing that its serial number. Do not rebuild it, read the Location
header.
Always read the Location header rather than rebuilding the address yourself.
The host and the language depend on the brand and on your request.
#Errors
| Code | Condition | What to do |
|---|---|---|
| 400 | The path's GTIN contains no digit, or contains more than 14 once separators are stripped. Message Invalid GTIN. | Fix the GTIN. It must fit in 14 digits at most. |
| 400 | The path's GTIN does not have 8, 12, 13 or 14 digits, or its last digit is not the check digit of the preceding ones. Message Invalid GTIN: the check digit does not match. | Copy the GTIN from the reference's barcode, then call again. |
| 400 | Your request arrived on a domain name we do not serve, or on a brand domain that is not verified yet. The response is plain text, no JSON. | Call https://api.sealtrust.io, or have the brand's domain verified before using it. |
| 404 | The serial number does not have the expected form: length other than 12, or a character outside the Crockford Base32 alphabet. Message Unknown GS1 Digital Link. | Check the value read off the label. |
| 404 | No unit carries this serial number. Message Unknown GS1 Digital Link. | Check the value. If the item was never registered with us, this code is final. |
| 404 | The unit exists, its model carries no usable GTIN. Message Unknown GS1 Digital Link. | Fill in the model's GTIN in your console. In the meantime, use the short address /p/{serial}. |
| 404 | The unit exists, the path's GTIN is not the one of its model. Message Unknown GS1 Digital Link. | Rebuild the link from the model's real GTIN. The GTIN and serial number pair must designate the same object. |
| 404 | The request arrived on a brand's verified domain name, and the unit belongs to another brand. Message Unknown GS1 Digital Link. | Call this link on our domain, or on the domain of the brand that owns the item. |
| 404 | The path is incomplete, for example /01/03701234567891/21 with no serial number. Message Not Found. | Complete the path. The four segments 01, the GTIN, 21 and the serial number are all mandatory. |
| 429 | More than 60 calls in 60 seconds from the same IP address, across every /01/ path. Message Rate limit exceeded: 60 requests per 60s. The response carries a 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 | 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. |
The five conditions that carry the message Unknown GS1 Digital Link return the
same code and the same body. You cannot tell them apart, and that is deliberate.
Distinct messages would let a scanner separate "this serial number does not
exist" from "this serial number exists under another GTIN", and therefore
confirm which numbers are real.
This endpoint does not return a 422. We take the GTIN and the serial number as strings of characters, then we check them ourselves: an unusable GTIN, or one whose check digit does not come out right, comes back as a 400, and a malformed serial number comes back as a 404.
#See also
GET /01/{gtin}, resolve a GS1 link that carries only a GTIN.GET /p/{serial}, translate the printed serial number into a consumer page address.GET /passport/{identifier}, read an item's published passport.- Physical identification, QR and NFC, choose the physical carrier and the exact form of the GS1 Digital Link.
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.