---
title: "GTIN barcode validator: EAN-13, UPC-A (GTIN-12), GTIN-8/14, GLN and SSCC check digits"
method: GET
path: "/v1/id/gs1"
tags: ["deterministic", "no-api-key", "id", "identifiers", "validation", "barcodes"]
---

# GTIN barcode validator: EAN-13, UPC-A (GTIN-12), GTIN-8/14, GLN and SSCC check digits

`GET /v1/id/gs1`

## Request

- Base URL: `https://api.duoleads.com`
- URL: `https://api.duoleads.com/v1/id/gs1`
- Auth: none declared

GTIN barcode validator: validate and convert GS1 identifiers: GTIN-8, GTIN-12 (UPC-A), GTIN-13 (EAN-13), GTIN-14, GLN and SSCC. Returns the check-digit verdict, the computed correct check digit, the GTIN-14 form with its indicator digit, and conversion between GTIN-8, -12, -13 and -14. No company-prefix split is returned: GS1's prefix-length table is no longer public data. Also computes a missing check digit from a partial code. Structure only, no product database.

**Output (`data`):** valid, type (gtin8|gtin12|gtin13|gtin14|gln|sscc|unknown), normalised, check_digit (the digit that should be there; string|null, null when the length is unsupported), check_digit_ok, gtin14 (zero-padded; string|null, null for sscc and unsupported lengths), indicator (string|null), reason (string|null), also_valid_as (present only for a gtin13, which is also a valid gln: [gln]; also after op=checkdigit and op=convert that produce a gtin13), converted (op=convert only: the value in the requested format; null when the input's check digit is wrong, in which case valid is false, reason is checksum and type and normalised are those of the input). The GS1 company-prefix boundary is not returned.

Deterministic: identical input returns identical output, so the response is served with `Cache-Control: private, max-age=31536000, immutable` (the paying client may cache it indefinitely; shared caches must not, because the response is paid).

Price: $0.05 USDC per call (x402, Base). Route contract version 1.0.0. Docs: https://api.duoleads.com/docs#id-gs1

## Query parameters

- `op` 'validate' | 'checkdigit' | 'convert'
- `value` string, required
- `to` 'gtin8' | 'gtin12' | 'gtin13' | 'gtin14'

## Response `200`

Paid and settled. `data` is route-specific; a route that judges its input returns 200 with `data.valid: false` for a bad value.

- object
  - `ok` unknown, required
  - `route` '/v1/id/gs1', required
  - `version` '1.0.0', required
  - `data` object, required: valid, type (gtin8|gtin12|gtin13|gtin14|gln|sscc|unknown), normalised, check_digit (the digit that should be there; string|null, null when the length is unsupported), check_digit_ok, gtin14 (zero-padded; string|null, null for sscc and unsupported lengths), indicator (string|null), reason (string|null), also_valid_as (present only for a gtin13, which is also a valid gln: [gln]; also after op=checkdigit and op=convert that produce a gtin13), converted (op=convert only: the value in the requested format; null when the input's check digit is wrong, in which case valid is false, reason is checksum and type and normalised are those of the input). The GS1 company-prefix boundary is not returned.
    - `valid` boolean
    - `type` string
    - `normalised` string
    - `check_digit` string, nullable
    - `check_digit_ok` boolean
    - `gtin14` string, nullable
    - `indicator` string, nullable
    - `reason` string, nullable
    - `also_valid_as` string[]
    - `converted` string, nullable
  - `meta` Meta, required
    - `disclaimer` string, required: Legal short disclaimer, identical on every response.
    - `request_id` string, required: ULID.
    - `computed_at` string, date-time, required: RFC 3339 UTC with milliseconds.
    - `price_usd` string, required: Decimal string, "0.05".
    - `deterministic` boolean, required
    - `sources` object[]: Present only on routes that read a vendored dataset: the exact edition that answered.
      - `id` string, required
      - `version` string, required
      - `published` string, required
      - `attribution` string
      - `licence` string

## Other responses

- `400`: MISSING_PARAM, INVALID_PARAM or UNSUPPORTED_VALUE. Not charged: validation runs before the paywall.
- `402`: Payment required. No valid PAYMENT-SIGNATURE was supplied: read the base64 PAYMENT-REQUIRED header (or this body) for the price ($0.05 = 50000 atomic USDC on eip155:8453), sign an EIP-3009 authorization and retry. Also returned when settlement is declined (PAYMENT_FAILED). Nothing is charged on a 402.
- `404`: NOT_FOUND: unknown path. Not charged.
- `405`: METHOD_NOT_ALLOWED: only GET and HEAD are served. Not charged.
- `413`: INPUT_TOO_LARGE: a parameter exceeded its documented maximum length. Not charged.
- `500`: INTERNAL: a server bug. Not charged.

---

[API](https://skmtc.dev/duoleads/apis/duo-data-utilities-iban-isin-vat-tax-id-barcode-timezone-geo.md) · [All operations](https://skmtc.dev/duoleads/apis/duo-data-utilities-iban-isin-vat-tax-id-barcode-timezone-geo/llms.txt) · [OpenAPI document](https://skmtc.dev/duoleads/apis/duo-data-utilities-iban-isin-vat-tax-id-barcode-timezone-geo/revisions/18790d59615e?raw)
