Method GET/01 /{gtin}
Resolve a GS1 Digital Link that carries only a GTIN, to the public page of the model's reference passport. 302 response, no API key.
On this page
- Authorization
- Rate limit
- Path and query parameters
- Writing the GTIN
- The check digit of the GTIN
- Choosing the language of the landing page
- Request headers
- Request body
- Example request
- Example response
- A single redirect
- On a brand's custom domain
- The scan is counted, keeping nothing of the reader
- When the brand shows its passports at a partner's
- Errors
- See also
You send a GTIN and you receive a redirect to the public page of the reference passport of the matching model. The GTIN, Global Trade Item Number, is the trade item number printed under the barcode. By the time you leave this page, you will know how to read the redirect, choose the language of the landing page, and recognize the error responses.
Full URL:
GET https://api.sealtrust.io/01/{gtin}The same endpoint also answers under the /v1 prefix, at
https://api.sealtrust.io/v1/01/{gtin}. Both URLs call the same code. A GS1
Digital Link carries the path /01/{gtin} without a prefix, so that is the
form a code reader encounters.
#
None, public endpoint. This endpoint answers without an API key, without an account and without a session cookie.
A partner API key presented here is not read. It changes neither the response nor the quotas of your plan.
#Rate limit
This endpoint has a dedicated and enforced cap of 60 calls per 60 seconds,
counted per calling IP address. The counter covers all /01/ paths together,
and it is a single one for every GTIN value: walking through a thousand
different GTINs consumes a thousand calls from the same budget.
Every response carries three headers that describe this counter.
| Header | Content |
|---|---|
X-RateLimit-Limit | the ceiling of the counter over the window |
X-RateLimit-Remaining | what is left to you in the current window |
X-RateLimit-Reset | the reset time, in seconds since January 1, 1970 |
X-RateLimit-Remaining stops at zero. Slow down before reaching it, and handle
the 429 code in your client from your very first integration.
This call does not touch the daily quota of an API key, and counts no piece.
#Path and query parameters
| Name | Type | Required | Description |
|---|---|---|---|
gtin | string | yes | The GTIN of the model. The GTIN-8, GTIN-12, GTIN-13 and GTIN-14 formats are accepted, with or without separators. Its last digit must be the check digit of the preceding ones: see below. |
This endpoint reads no query parameter. Everything you add after the ? is
ignored, including linkType, and is not carried over into the redirect. The
linkType parameter has an effect only on the links that designate a single
item, /p/{serial} and /01/{gtin}/21/{serial}. Here the destination is
already the passport.
#Writing the GTIN
The server brings your GTIN back to its canonical form of fourteen digits before the lookup. It removes every character that is not a digit, then it pads the result with zeros on the left up to fourteen digits.
These three spellings therefore designate the same model: 3701234567891,
03701234567891 and 3-701234-567891. It is always the fourteen digit form
that appears in the redirect.
A value that contains no digit returns 400. A value that contains more than
fourteen of them returns 400 as well: 0003701234567891 counts sixteen digits
and returns 400.
The lookup finds the model even if the brand recorded its GTIN in a shorter form, in GTIN-8, GTIN-12 or GTIN-13.
#The check digit of the GTIN
The last digit of a GTIN is its check digit: the GS1 modulo 10 rule computes it from the digits that precede it. The server recomputes it and compares it before looking for the model.
So send 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 this rule gets a 400 code, with the body
{"detail": "Invalid GTIN: the check digit does not match."}.
Copy the GTIN from the barcode of the reference. A typing mistake on a single digit then shows up as soon as you call.
#Choosing the language of the landing page
The redirect points to a page whose URL starts with the language. The server
picks that language from the Accept-Language header you send: it keeps the
first value of the header whose main language is fr or en, and it skips a
language you refuse with q=0.
With no header, or with no matching value, it looks at the domain called: on a
brand's verified domain name it picks the language that brand kept in its
console; on ours, or if it kept none, it picks en. A language you ask for
always comes before the brand's.
The server serves two languages, fr and en. The response carries
Vary: Accept-Language, since its destination depends on that header.
#Request headers
| Header | Required | Description |
|---|---|---|
Accept-Language | no | Picks the language of the landing page. Without this header, the brand's language on its own domain, otherwise en. |
#Request body
None. This request has no body.
#Example request
Resolution of the GTIN 03701234567891. The redirect is not followed, so that
the Location header can be read.
curl -i "https://api.sealtrust.io/01/03701234567891"const gtin = "03701234567891";
const response = await fetch(
`https://api.sealtrust.io/01/${encodeURIComponent(gtin)}`,
{ method: "GET", redirect: "manual" },
);
console.log(response.status);
console.log(response.headers.get("location"));import requests
gtin = "03701234567891"
response = requests.get(
f"https://api.sealtrust.io/01/{gtin}",
allow_redirects=False,
timeout=30,
)
print(response.status_code)
print(response.headers["Location"])To get the page in English, add the language header.
curl -i \
-H "Accept-Language: en" \
"https://api.sealtrust.io/01/03701234567891"const gtin = "03701234567891";
const response = await fetch(
`https://api.sealtrust.io/01/${encodeURIComponent(gtin)}`,
{
method: "GET",
redirect: "manual",
headers: { "Accept-Language": "en" },
},
);
console.log(response.status);
console.log(response.headers.get("location"));import requests
gtin = "03701234567891"
response = requests.get(
f"https://api.sealtrust.io/01/{gtin}",
headers={"Accept-Language": "en"},
allow_redirects=False,
timeout=30,
)
print(response.status_code)
print(response.headers["Location"])#Example response
HTTP code 302. The response has no body. All the information is in the
Location header.
HTTP/1.1 302 Found
Location: https://sealtrust.io/en/passport/01/03701234567891
Vary: Accept-Language
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 59
X-RateLimit-Reset: 1786000020
Content-Length: 0With the Accept-Language: en header, the same request returns this.
HTTP/1.1 302 Found
Location: https://sealtrust.io/en/passport/01/03701234567891
Vary: Accept-Language
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 58
X-RateLimit-Reset: 1786000020
Content-Length: 0| Header | Content |
|---|---|
Location | The full URL of the public page of the reference passport, at the path /{locale}/passport/01/{gtin}. The GTIN appears there in its fourteen digit form. |
Read the Location header and follow it. Do not hard-code the destination host
in your code: it changes with the domain the call arrives on, and it is the
response that is authoritative.
#A single redirect
This endpoint emits one hop only. The language is already resolved in the URL returned, so the landing page does not redirect you a second time.
The European registry of digital passports fetches identifier URLs in order to
validate them, and it accepts no more than three redirects. The Commission's
help desk gave that figure on August 31, 2026. It also states
that no maximum response size is published, but that a passport URL should avoid
large responses and resolve efficiently, and that URLs resolving to a local
endpoint, localhost or 127.0.0.1, are rejected.
One hop therefore leaves two of margin for your own infrastructure, for example
one redirect from http to https and one from www to the bare domain.
#On a brand's custom domain
When a brand serves this endpoint on its own domain name, verified with us, the redirect stays on that domain. A consumer who scans a product therefore never leaves the brand's domain.
On such a domain, only the trade references of that brand answer. A GTIN that belongs to another brand returns 404, with the same message as every other absence.
On a reseller's shared domain, the trade references of the reseller and of its client brands answer, and only those. The brand is the one that publishes the reference passport of this GTIN, read from our database, never from the domain name.
#The scan is counted, keeping nothing of the reader
Every GET call made by a browser that leads to a passport adds one to that
passport's number of scans for the current month. Not counted: robots and link
previews, HEAD requests, and a link that leads nowhere. No address, no
country and no time are kept: only the number of scans per passport and per
month exists. The brand reads it by model and by lot. The current month is
completed every ten minutes.
#When the brand shows its passports at a partner's
A brand, or its reseller, can show its passports in a partner's page, see
Show the passport in your page. When it has
switched this on in the console, this address answers 302 to the partner's
page instead of ours, with the parameters level (which is model),
gtin and lang. A request that carries ?linkType=dpp still gets our
passport.
#Errors
A browser, which asks for text/html in its Accept header, receives a short page in its language instead of JSON: same status, and the same text for every error, so it reveals nothing more than the JSON does. A program that asks for JSON, or sends no Accept header, receives the bodies described below.
The body of an error response contains a single field, detail.
{
"detail": "Unknown GS1 Digital Link"
}| Code | Condition | What to do |
|---|---|---|
| 400 | The value sent contains no digit, or contains more than fourteen of them. Message Invalid GTIN. | Fix the value. A valid GTIN counts at most fourteen digits, separators excluded. |
| 400 | The value sent does not count 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 barcode of the reference, then call again. |
| 404 | No public reference passport page answers for this GTIN on this domain. Message Unknown GS1 Digital Link. | Check the GTIN, then check in the console that the reference passport of this model is published and that its visibility is public. A passport attached to a single item never answers here. |
| 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 given by Retry-After, then try again. Follow your consumption with the X-RateLimit-* headers and spread your calls out over time. |
| 500 | Unexpected server error. Fixed body {"detail": "Internal Server Error"}. | Try again. The X-Request-Id header identifies the call, pass it on to us if it repeats. |
This endpoint does not return a 422. An unusable value comes out as a 400, and a GTIN with no public page comes out as a 404.
#See also
GET /01/{gtin}/21/{serial}, resolve a GS1 link that carries a GTIN and a serial number.GET /passport/01/{gtin}, read the published passport of a model, from its GTIN.GET /p/{serial}, translate the printed serial number into a consumer page URL.- 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.