curl --request GET \
--url https://www.closient.com/resolver/api/v1/verify/01/{gtin}/21/{serial_number}import requests
url = "https://www.closient.com/resolver/api/v1/verify/01/{gtin}/21/{serial_number}"
response = requests.get(url)
print(response.text)const options = {method: 'GET'};
fetch('https://www.closient.com/resolver/api/v1/verify/01/{gtin}/21/{serial_number}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://www.closient.com/resolver/api/v1/verify/01/{gtin}/21/{serial_number}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://www.closient.com/resolver/api/v1/verify/01/{gtin}/21/{serial_number}"
req, _ := http.NewRequest("GET", url, nil)
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://www.closient.com/resolver/api/v1/verify/01/{gtin}/21/{serial_number}")
.asString();require 'uri'
require 'net/http'
url = URI("https://www.closient.com/resolver/api/v1/verify/01/{gtin}/21/{serial_number}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
response = http.request(request)
puts response.read_body{
"anomalies": [
{
"description": "Serial previously scanned at POS at a different retail location.",
"priorEvent": {
"location": "49.2827,-123.1207",
"retailer": "RetailerA",
"scannedAt": "2026-04-10T14:30:00Z"
},
"type": "duplicate_retail_scan"
}
],
"gtin": "09521101530018",
"recommendation": "flag_for_review",
"scanHistory": {
"consumerScans": 3,
"distinctRetailLocations": 2,
"firstSeen": "2026-04-01T10:00:00Z",
"retailerScans": 2,
"totalScans": 5
},
"serialNumber": "DUPE001",
"verificationStatus": "suspect",
"verifiedAt": "2026-05-09T20:00:00Z"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "GTIN must be 8-14 digits.",
"error": "invalid_gtin"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}Verify a serialized GTIN (gs1:verificationService)
Verify a serialized GTIN scan.
Tiered response:
- Unauthenticated — minimal payload (
verificationStatus,gtin,serialNumber,verifiedAt,recommendation). Throttled at 60 requests / hour per IP. Unknown serials return the same shape as known-authentic to prevent enumeration. - Authenticated (X-API-Key) — full payload including
scanHistorycounters andanomaliesarray. No anonymous rate limit.
Path parameters mirror GS1 Digital Link AI ordering: 01 (GTIN) +
21 (serial). Returns 422 for malformed GTIN or serial.
Experimental (v1): This linkType response shape is published as a
v1 experimental contract. The auth-tier split, response fields, and
verificationStatus enum may change before the first external
integration freezes the shape.
curl --request GET \
--url https://www.closient.com/resolver/api/v1/verify/01/{gtin}/21/{serial_number}import requests
url = "https://www.closient.com/resolver/api/v1/verify/01/{gtin}/21/{serial_number}"
response = requests.get(url)
print(response.text)const options = {method: 'GET'};
fetch('https://www.closient.com/resolver/api/v1/verify/01/{gtin}/21/{serial_number}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://www.closient.com/resolver/api/v1/verify/01/{gtin}/21/{serial_number}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://www.closient.com/resolver/api/v1/verify/01/{gtin}/21/{serial_number}"
req, _ := http.NewRequest("GET", url, nil)
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://www.closient.com/resolver/api/v1/verify/01/{gtin}/21/{serial_number}")
.asString();require 'uri'
require 'net/http'
url = URI("https://www.closient.com/resolver/api/v1/verify/01/{gtin}/21/{serial_number}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
response = http.request(request)
puts response.read_body{
"anomalies": [
{
"description": "Serial previously scanned at POS at a different retail location.",
"priorEvent": {
"location": "49.2827,-123.1207",
"retailer": "RetailerA",
"scannedAt": "2026-04-10T14:30:00Z"
},
"type": "duplicate_retail_scan"
}
],
"gtin": "09521101530018",
"recommendation": "flag_for_review",
"scanHistory": {
"consumerScans": 3,
"distinctRetailLocations": 2,
"firstSeen": "2026-04-01T10:00:00Z",
"retailerScans": 2,
"totalScans": 5
},
"serialNumber": "DUPE001",
"verificationStatus": "suspect",
"verifiedAt": "2026-05-09T20:00:00Z"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "GTIN must be 8-14 digits.",
"error": "invalid_gtin"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}Path Parameters
GS1 GTIN-8/12/13/14 (AI 01) of the serialized trade item. Validated as 8-14 ASCII digits; non-digit input returns 422 invalid_gtin.
Per-trade-item serial number (AI 21). 1-20 printable ASCII characters per GS1 General Specifications; out-of-range input returns 422 invalid_serial.
Response
OK
Authenticated response payload — minimal plus history and anomalies.
Verification status. Unknown serials always return authentic (enumeration protection). The internal unverified state is never exposed -- it collapses to authentic on the public contract until Phase 3 ships positive-authentication rules.
authentic, suspect, counterfeit_suspected, serialization_error Echoed GTIN-8/12/13/14 from the request path.
Echoed AI 21 serial component from the request path.
Server timestamp of when this verification response was generated.
Action recommendation. proceed for authentic and unknown serials; flag_for_review for suspect, counterfeit_suspected, or serialization_error.
proceed, flag_for_review Aggregate scan counters. null when the serial has never been observed (the enumeration-safe path returns full fields populated to zero on the unauth tier; on the auth tier we expose the truth and return null so callers can distinguish 'never seen' from 'seen-but-zero'.
Show child attributes
Show child attributes
{
"consumerScans": 3,
"distinctRetailLocations": 2,
"firstSeen": "2026-04-01T10:00:00Z",
"retailerScans": 2,
"totalScans": 5
}
Anomaly rules triggered for this serial. Empty when no rule has fired. Phase 1 only emits duplicate_retail_scan.
Show child attributes
Show child attributes