Documentation Index
Fetch the complete documentation index at: https://docs.closient.com/llms.txt
Use this file to discover all available pages before exploring further.
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. Closient will announce a stable v1.0 once an external scanner or 2D-cert tool integrates against the response.
The gs1:verificationService linkType returns the verification status of a serialized trade item — a GTIN + AI 21 serial pair scanned against the Closient resolver. Two tiers are exposed:
| Tier | Caller | Auth | Rate limit | Payload |
|---|
| Minimal | Consumer scan (browser, native scanner) | none | 60 / hour / IP | 5 fields, no scan history |
| Full | Brand dashboard, retailer POS, 2D-cert tool | X-API-Key | per-key (default 300/min) | scan history, anomaly entries, prior-event hints |
Endpoint
GET https://www.closient.com/resolver/api/v1/verify/01/{gtin}/21/{serial}
Path parameters mirror the GS1 Digital Link AI ordering used elsewhere in the resolver (01 = GTIN, 21 = serial).
| Parameter | Format | Notes |
|---|
gtin | 8–14 digit GTIN | GTIN-8/12/13/14; non-digit input returns 422 invalid_gtin. |
serial | 1–20 printable ASCII characters | AI 21. Out-of-range characters return 422 invalid_serial. |
Authentication
Authenticated calls supply an organization API key in the X-API-Key header. The same key shape used elsewhere in the Closient API is accepted (csb_*).
GET /resolver/api/v1/verify/01/09521101530018/21/SN001 HTTP/1.1
Host: www.closient.com
X-API-Key: csb_<body>_<checksum>
Calls without X-API-Key (or with an unknown key) fall through to the minimal-tier response and are subject to the IP rate limit.
Unauthenticated response (consumer)
{
"verificationStatus": "authentic",
"gtin": "09521101530018",
"serialNumber": "SN001",
"verifiedAt": "2026-05-09T20:00:00Z",
"recommendation": "proceed"
}
| Field | Type | Description |
|---|
verificationStatus | enum | authentic, suspect, counterfeit_suspected, serialization_error |
gtin | string | Echoed GTIN from the request path |
serialNumber | string | Echoed AI 21 serial from the request path |
verifiedAt | ISO 8601 timestamp | When this response was generated |
recommendation | enum | proceed for authentic / unverified / unknown serials, flag_for_review otherwise |
Enumeration protection
Serials we have never seen return the same shape as known-authentic serials (verificationStatus: "authentic", recommendation: "proceed"). Consumer-facing scanners cannot use this endpoint to probe whether a particular serial is in the Closient database. Brands needing the truthful “we have not seen this serial” signal must authenticate — the auth-tier response returns scanHistory: null for never-seen serials.
Rate limit
Unauthenticated calls are limited to 60 requests per hour per client IP. The 61st call returns 429 Too Many Requests with a Retry-After header indicating the seconds remaining in the bucket.
Authenticated response (brand / retailer)
{
"verificationStatus": "suspect",
"gtin": "09521101530018",
"serialNumber": "DUPE001",
"verifiedAt": "2026-05-09T20:00:00Z",
"scanHistory": {
"totalScans": 5,
"retailerScans": 2,
"consumerScans": 3,
"distinctRetailLocations": 2,
"firstSeen": "2026-04-01T10:00:00Z"
},
"anomalies": [
{
"type": "duplicate_retail_scan",
"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"
}
}
],
"recommendation": "flag_for_review"
}
The auth-tier response is a strict superset of the unauth-tier response — every consumer field is preserved verbatim.
scanHistory
| Field | Type | Description |
|---|
totalScans | int ≥ 0 | All scan-context counters summed |
retailerScans | int ≥ 0 | Authenticated retailer-system scans |
consumerScans | int ≥ 0 | Browser / unauthenticated scans |
distinctRetailLocations | int ≥ 0 | Unique POS locations that have scanned this serial |
firstSeen | ISO 8601 timestamp | Earliest observed scan |
scanHistory is null when the serial has never been observed by the resolver (auth tier only — the unauth tier omits the field entirely).
anomalies
Empty array when no rule has fired. Phase 1 emits a single rule:
type | Trigger | priorEvent |
|---|
duplicate_retail_scan | retailerScans ≥ 2 AND distinctRetailLocations ≥ 2 | Most recent retailer scan from a different POS |
Each anomaly entry carries:
| Field | Type | Description |
|---|
type | string | Stable rule identifier |
description | string | Human-readable explanation |
priorEvent.location | string | lat,lon coordinates (4 decimal places), GLN:<gln> when only a GLN is captured, or empty string |
priorEvent.retailer | string | Retailer organization name when the prior scan was authenticated, empty otherwise |
priorEvent.scannedAt | ISO 8601 timestamp | Prior event time (UTC) |
Phase 3 (tracked in C-2292) layers on the remaining rules — excessive scans, geographic impossibility, post-sale re-entry, serialization errors, mass-scan — each with its own type value.
Status values
| Value | Meaning | Default recommendation |
|---|
authentic | No negative signals; treat as genuine | proceed |
suspect | At least one anomaly rule has fired | flag_for_review |
counterfeit_suspected | Operator-confirmed counterfeit (Phase 3+) | flag_for_review |
serialization_error | Brand serialization scheme violated (Phase 3+) | flag_for_review |
The unverified internal state is never returned to consumers — it collapses to authentic for the public contract until Phase 3 ships positive-authentication rules.
Errors
| Status | Body | When |
|---|
| 200 | Verification payload | Always for valid path parameters |
| 422 | {"error": "invalid_gtin", "detail": "..."} | GTIN is not 8–14 digits |
| 422 | {"error": "invalid_serial", "detail": "..."} | Serial not 1–20 printable ASCII characters |
| 429 | Rate-limit envelope, Retry-After header | Anonymous IP exceeded 60/hour |
Stability guarantees
This linkType is experimental v1. Until at least one external scanner / 2D-cert tool pins to the contract, the following may change with notice in the changelog:
- Field names (
verificationStatus, recommendation, etc.)
- The
verificationStatus enum set
- The auth-tier split (which fields are unauth vs auth)
- The
recommendation mapping table
- The shape of
anomalies entries
Stable in all v1 revisions:
- The path layout (
/resolver/api/v1/verify/01/<gtin>/21/<serial>)
- The
X-API-Key auth scheme
- The 60/hour anonymous rate limit
- The enumeration-protection guarantee (unknown serial = known-authentic shape)
When the contract freezes, Closient will announce v1.0 in the API changelog.