Bulk Validate a CSV of Barcodes with Python and an API
A supplier just sent you a spreadsheet with 5,000 EANs and you need to know which ones are broken before they hit your ERP or your Amazon feed. Here is a short, tested Python script that does it in a few dozen API calls.
Browser tool or API?
First, the honest question: do you need an API at all? If this is a one-off job, paste the column into the free bulk barcode validator — it runs entirely in your browser, needs no signup, and flags every bad check digit instantly. (If you want to know what that check digit actually is, see how check digits work.)
Reach for the API when the job repeats: a nightly import, a validation step inside a data pipeline, files that arrive weekly from suppliers, or checks that must run on a server with nobody around to paste anything. That is what this guide covers.
The batch endpoint
The CodeClassify API validates GTINs in batches: POST /v1/gtin/validate accepts up to 100 codes per request in a JSON body of the form {"codes": ["...", "..."]}, authenticated with an X-Api-Key header. Crucially, one request counts as one API call no matter how many codes it carries, so you always want to fill batches to 100. The response echoes every code back in the same order:
{"ok":true,"count":3,"valid":2,"results":[
{"input":"036000291452","code":"036000291452","format":"UPC-A","valid":true},
{"input":"4006381333931","code":"4006381333931","format":"EAN-13","valid":true},
{"input":"036000291453","code":"036000291453","format":"UPC-A","valid":false,
"expected_check_digit":2,"corrected":"036000291452"}
]}
Note the last entry: when a check digit is wrong, the API does not just say “invalid” — it returns the expected_check_digit and the corrected code, which is exactly what you want to write back into your spreadsheet.
The complete script
Python 3 only, standard library only — nothing to pip install. It reads one column from a CSV, sends the codes in chunks of 100, and writes a results CSV next to it.
import csv, json, sys, time
import urllib.request
API_URL = "https://codeclassify-api.rosariovitale0096.workers.dev/v1/gtin/validate"
API_KEY = "ccl_YOUR_KEY_HERE" # free key: https://code-classify.com/api/
CHUNK = 100 # API maximum per request
def validate_batch(codes):
payload = json.dumps({"codes": codes}).encode("utf-8")
req = urllib.request.Request(
API_URL, data=payload, method="POST",
headers={"Content-Type": "application/json", "X-Api-Key": API_KEY},
)
with urllib.request.urlopen(req, timeout=30) as resp:
return json.load(resp)["results"]
def main(in_path, out_path, column):
with open(in_path, newline="", encoding="utf-8-sig") as f:
rows = list(csv.DictReader(f))
codes = [(row.get(column) or "").strip() for row in rows]
results = []
for i in range(0, len(codes), CHUNK):
results.extend(validate_batch(codes[i:i + CHUNK]))
time.sleep(0.2) # small pause between calls
with open(out_path, "w", newline="", encoding="utf-8") as f:
w = csv.writer(f)
w.writerow([column, "valid", "format", "corrected"])
for code, r in zip(codes, results):
w.writerow([code, r["valid"], r.get("format", ""),
r.get("corrected", "")])
print(f"Done: {sum(r['valid'] for r in results)} of {len(results)} valid")
if __name__ == "__main__":
main(sys.argv[1], sys.argv[2], sys.argv[3])
Run it as: python validate_csv.py products.csv products-checked.csv barcode — input file, output file, name of the barcode column.
Step by step:
- Reading:
csv.DictReaderkeeps every value as a string, so leading zeros survive. Theutf-8-sigencoding silently swallows the byte-order mark Excel puts at the start of CSV exports — without it, the first column name becomesbarcodeand the lookup fails. - Chunking: the loop slices the list into groups of 100. A 5,000-row file becomes exactly 50 requests. The short
sleepis just politeness between calls. - Alignment: the API returns results in input order and one per code, so
zip(codes, results)pairs them safely — no matching logic needed. - Writing: each row gets the verdict, the detected format (UPC-A, EAN-13, GTIN-14…), and, when the check digit was mistyped, the corrected code ready to copy back.
Worked example: real input, real output
This input file exercises the interesting cases:
sku,barcode,name
A-1,036000291452,Known-valid UPC-A
A-2,4006381333931,Known-valid EAN-13
A-3,036000291453,Wrong check digit
A-4,978-0-306-40615-7,ISBN-13 with hyphens
A-5,12345,Too short
A-6,00036000291452,GTIN-14 padded
Running the script prints Done: 4 of 6 valid and writes:
barcode,valid,format,corrected
036000291452,True,UPC-A,
4006381333931,True,EAN-13,
036000291453,False,UPC-A,036000291452
978-0-306-40615-7,True,EAN-13,
12345,False,,
00036000291452,True,GTIN-14,
Three things worth noticing. The mistyped 036000291453 comes back with its fix, 036000291452, in the last column. The hyphenated ISBN validates as an EAN-13, because an ISBN-13 is a GTIN and non-digits are stripped server-side. And the 14-digit code with three leading zeros is a valid GTIN-14 — those zeros are load-bearing.
Edge cases that bite
- Leading zeros. The single most common failure. If the CSV was ever opened and re-saved in Excel with the barcode column as a number,
036000291452has already become36000291452(11 digits, invalid length) before your script ever runs. Keep the column as text — the fix is in Excel leading zeros and barcodes. - Wrong lengths. Codes that clean up to anything other than 8, 12, 13 or 14 digits return
"valid": falsewith"reason": "wrong_length"— that is a data problem in your file, not a check-digit failure, and it usually points back to the zeros above. - Empty cells. An empty cell is sent as an empty string and comes back as a wrong-length failure, keeping row alignment intact. Filter them out beforehand if you would rather not spend batch slots on blanks.
- Blind retries. If a request fails (network hiccup, 429 when quota runs out), do not loop over the same chunk forever: failed requests are not billed, but a bug that retries a successful chunk will be. Check the HTTP status before retrying.
A checksum is not a registry
Everything above verifies internal consistency: the check digit matches the other digits (see the GTIN check digit calculator for singles, or our methodology for the exact algorithms). A code can pass and still belong to no real product — nobody can tell you that from math alone; only GS1's registry knows what has actually been assigned. Treat checksum validation as the cheap first gate that catches typos and truncation, not as proof a barcode is registered.
Validating at scale? The free tier and the math
Try the endpoint from your terminal — a free key from the API page includes 10 calls per month:
curl -X POST https://codeclassify-api.rosariovitale0096.workers.dev/v1/gtin/validate \
-H "X-Api-Key: ccl_YOUR_KEY_HERE" \
-H "Content-Type: application/json" \
-d '{"codes":["036000291452","4006381333931","036000291453"]}'
Because billing is per request, the free tier's 10 calls cover up to 1,000 codes a month in full batches. Beyond that, pay-per-use is $0.01 per call — a $10 credit pack buys 1,000 calls, i.e. up to 100,000 validated codes, and the PRO plan ($9.99/month) includes 2,000 calls. The same batch pattern works for the other endpoints too: /v1/iban/validate takes {"ibans": [...]}, /v1/vat/validate takes {"vats": [...]}, /v1/luhn/validate takes {"numbers": [...]} — swap the URL and the field name in the script and the rest is identical.
FAQ
How many barcodes can I validate for free with the API?
The free API key includes 10 calls per month, and each call to POST /v1/gtin/validate accepts up to 100 codes, so a free key covers up to 1,000 barcodes per month if you send full batches. For occasional manual checks, the in-browser bulk barcode validator is free with no key at all.
Does each barcode count as one API call?
No. Billing is per HTTP request, not per code. A request carrying 100 codes consumes exactly one call from your quota or one credit, the same as a request carrying a single code. That is why the script in this guide always fills batches to the 100-code maximum.
Do I need to strip hyphens or leading zeros before sending codes?
Hyphens and spaces, no: the API strips every non-digit character before validating, so 978-0-306-40615-7 is checked as 9780306406157. Leading zeros, never strip them: the digit count decides whether a code is treated as GTIN-8, UPC-A, EAN-13 or GTIN-14, so 00036000291452 and 036000291452 are different codes. Keep the barcode column formatted as text so your spreadsheet does not delete the zeros for you.
Get a free API key
Sign up on the API page for a free key — 10 batch calls (up to 1,000 codes) per month, no card required — and point the script above at your own CSV.
This guide is for general information only. Checksum validation confirms internal consistency of a number, not that a code is officially registered, assigned, or in use. For barcode assignment consult GS1. API pricing shown is as of publication; see the API page for current plans.