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.

What it does not do. This API answers questions about the number, not about products. There is no universal product database, because GS1 only ever registered prefixes to companies and never individual items. Why that is, explained here.

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/v1

Machine 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.json

Authentication and limits

There is no authentication. Requests are limited per IP address.

HeaderMeaning
X-RateLimit-LimitRequests allowed per hour. Currently 120.
X-RateLimit-RemainingRequests left in the current window.
X-RateLimit-ResetSeconds until the window resets.
Cache-Controlpublic, 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

GET/gtin/validate
ParameterTypeRequiredNotes
gtinstringyesGTIN-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

GET/gtin/check-digit
ParameterTypeRequiredNotes
payloadstringyesThe 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

GET/gtin/issuer
ParameterTypeRequiredNotes
gtinstringyesA 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

GET/gs1/prefixes

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

CodeWhen
200Request 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.
400A required parameter is missing entirely.
429Rate 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   = 5012345678900

The 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.

The prefix tells you which GS1 organisation issued the block. It is not where a product was made and not who owns the number today. Prefixes are traded and companies relocate. Treat it as an origin hint, never as proof.

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.