Free GS1 Barcode API: GTIN Validation, Check Digits and Prefix Lookup
A free REST API for GTIN and barcode numbers. It validates check digits, calculates them, and tells you which GS1 member organisation issued a prefix. No key, no sign up.
Quick start
Every endpoint is a GET, returns JSON, and sends Access-Control-Allow-Origin: * so it works straight from a browser.
curl "https://toolsque.com/wp-json/toolsque/v1/gtin/validate?gtin=5901234123457"const res = await fetch(
"https://toolsque.com/wp-json/toolsque/v1/gtin/validate?gtin=5901234123457"
);
const data = await res.json();
console.log(data);import requests
r = requests.get(
"https://toolsque.com/wp-json/toolsque/v1/gtin/validate?gtin=5901234123457",
timeout=10,
)
print(r.json())$r = wp_remote_get(
'https://toolsque.com/wp-json/toolsque/v1/gtin/validate?gtin=5901234123457'
);
$data = json_decode( wp_remote_retrieve_body( $r ), true );Base URL
https://toolsque.com/wp-json/toolsque/v1Machine readable description
An OpenAPI 3.1 document is published at /openapi.json. Import it into Postman, Insomnia or Swagger UI, or generate a client from it.
https://toolsque.com/wp-json/toolsque/v1/openapi.jsonAuthentication and limits
There is no authentication. Requests are limited per IP address.
| Header | Meaning |
|---|---|
X-RateLimit-Limit | Requests allowed per hour. Currently 120. |
X-RateLimit-Remaining | Requests left in the current window. |
X-RateLimit-Reset | Seconds until the window resets. |
Cache-Control | public, max-age=86400. Answers are deterministic, so cache them. |
If you are validating in bulk, cache locally. The prefix table changes roughly never and a check digit for a given payload never changes at all.
Validate a GTIN
| Parameter | Type | Required | Notes |
|---|---|---|---|
gtin | string | yes | GTIN-8, 12, 13 or 14. Non digit characters are stripped, so spaces and hyphens are fine. |
curl "https://toolsque.com/wp-json/toolsque/v1/gtin/validate?gtin=5901234123457"const res = await fetch(
"https://toolsque.com/wp-json/toolsque/v1/gtin/validate?gtin=5901234123457"
);
const data = await res.json();
console.log(data);import requests
r = requests.get(
"https://toolsque.com/wp-json/toolsque/v1/gtin/validate?gtin=5901234123457",
timeout=10,
)
print(r.json())$r = wp_remote_get(
'https://toolsque.com/wp-json/toolsque/v1/gtin/validate?gtin=5901234123457'
);
$data = json_decode( wp_remote_retrieve_body( $r ), true );Response
{
"valid": true,
"input": "5901234123457",
"type": "GTIN-13",
"length": 13,
"payload": "590123412345",
"check_digit": 7,
"expected": 7,
"prefix": "590",
"organisation": "GS1 Poland",
"restricted": false
}When the check digit is wrong you get the reason and the corrected number, so you can repair a typo without a second call:
{
"valid": false,
"check_digit": 1,
"expected": 0,
"reason": "Check digit is 1 but should be 0.",
"correct": "5012345678900",
"prefix": "501",
"organisation": "GS1 UK"
}Calculate a check digit
| Parameter | Type | Required | Notes |
|---|---|---|---|
payload | string | yes | The number without its check digit. 7, 11, 12 or 13 digits, producing GTIN-8, 12, 13 and 14. |
curl "https://toolsque.com/wp-json/toolsque/v1/gtin/check-digit?payload=501234567890"const res = await fetch(
"https://toolsque.com/wp-json/toolsque/v1/gtin/check-digit?payload=501234567890"
);
const data = await res.json();
console.log(data);import requests
r = requests.get(
"https://toolsque.com/wp-json/toolsque/v1/gtin/check-digit?payload=501234567890",
timeout=10,
)
print(r.json())$r = wp_remote_get(
'https://toolsque.com/wp-json/toolsque/v1/gtin/check-digit?payload=501234567890'
);
$data = json_decode( wp_remote_retrieve_body( $r ), true );{
"payload": "501234567890",
"check_digit": 0,
"gtin": "5012345678900",
"type": "GTIN-13"
}Look up an issuer
| Parameter | Type | Required | Notes |
|---|---|---|---|
gtin | string | yes | A full GTIN, or just the leading three digits. |
curl "https://toolsque.com/wp-json/toolsque/v1/gtin/issuer?gtin=890"const res = await fetch(
"https://toolsque.com/wp-json/toolsque/v1/gtin/issuer?gtin=890"
);
const data = await res.json();
console.log(data);import requests
r = requests.get(
"https://toolsque.com/wp-json/toolsque/v1/gtin/issuer?gtin=890",
timeout=10,
)
print(r.json())$r = wp_remote_get(
'https://toolsque.com/wp-json/toolsque/v1/gtin/issuer?gtin=890'
);
$data = json_decode( wp_remote_retrieve_body( $r ), true );{ "input": "890", "prefix": "890", "organisation": "GS1 India", "restricted": false }A prefix GS1 has never assigned returns "organisation": null with a note. That is a correct answer, not an error.
Fetch the prefix table
Takes no parameters. Returns all 87 ranges as JSON, the same data rendered further down this page.
curl "https://toolsque.com/wp-json/toolsque/v1/gs1/prefixes"const res = await fetch(
"https://toolsque.com/wp-json/toolsque/v1/gs1/prefixes"
);
const data = await res.json();
console.log(data);import requests
r = requests.get(
"https://toolsque.com/wp-json/toolsque/v1/gs1/prefixes",
timeout=10,
)
print(r.json())$r = wp_remote_get(
'https://toolsque.com/wp-json/toolsque/v1/gs1/prefixes'
);
$data = json_decode( wp_remote_retrieve_body( $r ), true );Status codes and errors
| Code | When |
|---|---|
200 | Request understood. This includes invalid GTINs, which return "valid": false with a plain English reason. A number failing its check digit is a result, not an error. |
400 | A required parameter is missing entirely. |
429 | Rate limit reached. Retry-After tells you how long to wait. |
How the check digit works
Take the payload, meaning the number without its final digit. Walking from the rightmost digit leftwards, multiply alternately by 3 and 1, starting with 3. Sum the results. The check digit is whatever you must add to reach the next multiple of ten.
payload 5 0 1 2 3 4 5 6 7 8 9 0
weights 1 3 1 3 1 3 1 3 1 3 1 3
sum = 90
check digit = (10 - (90 mod 10)) mod 10 = 0
full GTIN = 5012345678900The rule is length independent, which is why the same arithmetic covers GTIN-8 through GTIN-14.
Prefix reference
Prefixes are compared against the first three digits of the GTIN-13 form. A UPC-A gains a leading zero first, which is why American numbers beginning 0 resolve to GS1 US. A GTIN-14 drops its packaging indicator digit before the lookup.
All 87 ranges are available two ways:
- As JSON from
/gs1/prefixes, described above. - As a searchable table on our barcode country code lookup, where you can also paste a real barcode and get the issuer and check digit verdict without writing any code.
Questions
Does this API look up products?
No. It answers questions about the number itself. There is no universal product database, because GS1 only ever registered prefixes to companies, never individual items. Services that offer product lookup compiled that data themselves, which is why their coverage is uneven.
Do I need an API key?
No. There is no key, no sign up and no account. The limit is 120 requests per IP per hour, which is generous for validation work since every response is cacheable for a day.
Does the prefix tell me where a product was made?
No. It tells you which GS1 member organisation issued that block of numbers. Prefixes are traded between companies and companies relocate, so treat it as an origin hint and never as proof of manufacture or ownership.
Which GTIN lengths are supported?
GTIN-8, GTIN-12 (UPC-A), GTIN-13 (EAN-13) and GTIN-14 (ITF-14). The check digit rule is length independent, so the same arithmetic covers all four.
Can I use it commercially?
Yes, in anything, with no attribution required. Nothing checks for a backlink and nothing ever will.
Use and attribution
Use it in anything, commercial included. No attribution required. A link back is welcome but nothing checks for one.
Prefer to work in the browser? The same logic runs client side in our barcode scanner and barcode generator, where nothing is uploaded anywhere.
