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

# 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 `scanHistory`
  counters and `anomalies` array. 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.




## OpenAPI

````yaml /openapi/openapi-resolver.json get /resolver/api/v1/verify/01/{gtin}/21/{serial_number}
openapi: 3.1.0
info:
  title: Resolver API
  version: 1.0.0
  description: >
    GS1 Digital Link resolution with content negotiation and linkset support.


    ## Authentication


    All endpoints require an API key passed via the `X-API-Key` HTTP header,
    unless otherwise noted.


    ```

    X-API-Key: csb_<body>_<checksum>

    ```


    Generate API keys in **Settings > API Keys** in your dashboard, or via the
    Account API.

    Session-based (cookie) authentication is also accepted for browser-based
    access.


    ## Rate Limits


    | Tier        | Requests / minute | Requests / day |

    |-------------|-------------------|----------------|

    | Default     | 300               | 10,000         |

    | Custom      | Contact us        | Contact us     |


    Rate-limit headers are included on every response so callers can
    self-throttle without

    hitting our 429s ("informed governor"):


    - `RateLimit-Policy` — every active window, e.g. `300;w=60, 10000;w=86400`

    - `RateLimit-Limit` — quota for the **most-restrictive** currently-active
    window

    - `RateLimit-Remaining` — requests left in that window

    - `RateLimit-Reset` — seconds until that window resets (relative; clock-skew
    safe)


    Legacy `X-RateLimit-*` aliases are also emitted for back-compat.
    `X-RateLimit-Reset`

    keeps the absolute Unix-timestamp shape to avoid breaking existing
    consumers.


    When rate-limited, you receive `429 Too Many Requests` with a
    `retry_after_seconds` field

    in the error envelope and a `Retry-After` header.


    ## Pagination


    List endpoints return paginated results in this envelope:


    ```json

    {
      "data": [...],
      "pagination": {
        "page": 1,
        "page_size": 25,
        "total_count": 342,
        "total_pages": 14,
        "has_next": true,
        "has_previous": false
      }
    }

    ```


    Use `?page=2&page_size=50` query parameters. Maximum page size is 100.


    ## Error Responses


    All errors conform to [RFC 9457 Problem
    Details](https://www.rfc-editor.org/rfc/rfc9457)

    with `Content-Type: application/problem+json`:


    ```json

    {
      "type": "https://closient.com/docs/errors/not_found",
      "title": "Not Found",
      "status": 404,
      "detail": "The requested resource was not found.",
      "error_code": "not_found",
      "retryable": false,
      "timestamp": "2026-03-31T12:00:00+00:00"
    }

    ```


    Common error codes: `unauthorized` (401), `forbidden` (403), `not_found`
    (404),

    `validation_error` (422), `rate_limited` (429), `internal_error` (500).
  termsOfService: https://www.closient.com/terms/
servers:
  - url: https://www.closient.com
security: []
tags:
  - name: Resolver
    description: GS1 Digital Link resolution with content negotiation and linkset support.
  - name: Custom URLs
    description: >-
      Reusable custom-URL catalog (C-3339) — create, list, edit, and delete the
      custom redirect destinations that resolution rules point at.
  - name: Verification
    description: >-
      Serial verification (`gs1:verificationService`) — tiered, rate-limited
      responses for serialized GTIN scans. Experimental v1; see the
      verification-service guide.
externalDocs:
  description: Closient Documentation
  url: https://docs.closient.com
paths:
  /resolver/api/v1/verify/01/{gtin}/21/{serial_number}:
    get:
      tags:
        - Verification
      summary: Verify a serialized GTIN (gs1:verificationService)
      description: |

        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 `scanHistory`
          counters and `anomalies` array. 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.
      operationId: apps_resolver_api_verification_verify_serial
      parameters:
        - in: path
          name: gtin
          schema:
            description: >-
              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``.
            title: Gtin
            type: string
          required: true
          description: >-
            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``.
        - in: path
          name: serial_number
          schema:
            description: >-
              Per-trade-item serial number (AI 21). 1-20 printable ASCII
              characters per GS1 General Specifications; out-of-range input
              returns 422 ``invalid_serial``.
            title: Serial Number
            type: string
          required: true
          description: >-
            Per-trade-item serial number (AI 21). 1-20 printable ASCII
            characters per GS1 General Specifications; out-of-range input
            returns 422 ``invalid_serial``.
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VerificationOutFull'
        '422':
          description: Unprocessable Content
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VerificationErrorOut'
components:
  schemas:
    VerificationOutFull:
      description: Authenticated response payload — minimal plus history and anomalies.
      examples:
        - 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'
      properties:
        verificationStatus:
          $ref: '#/components/schemas/VerificationStatusEnum'
          description: >-
            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.
        gtin:
          description: Echoed GTIN-8/12/13/14 from the request path.
          title: Gtin
          type: string
        serialNumber:
          description: Echoed AI 21 serial component from the request path.
          title: Serialnumber
          type: string
        verifiedAt:
          description: Server timestamp of when this verification response was generated.
          format: date-time
          title: Verifiedat
          type: string
        recommendation:
          $ref: '#/components/schemas/VerificationRecommendationEnum'
          description: >-
            Action recommendation. ``proceed`` for ``authentic`` and unknown
            serials; ``flag_for_review`` for ``suspect``,
            ``counterfeit_suspected``, or ``serialization_error``.
        scanHistory:
          anyOf:
            - $ref: '#/components/schemas/ScanHistoryOut'
            - type: 'null'
          description: >-
            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'.
        anomalies:
          description: >-
            Anomaly rules triggered for this serial. Empty when no rule has
            fired. Phase 1 only emits ``duplicate_retail_scan``.
          items:
            $ref: '#/components/schemas/AnomalyOut'
          title: Anomalies
          type: array
      required:
        - verificationStatus
        - gtin
        - serialNumber
        - verifiedAt
        - recommendation
      title: VerificationOutFull
      type: object
    VerificationErrorOut:
      description: Validation error envelope for malformed GTIN/serial inputs.
      examples:
        - detail: GTIN must be 8-14 digits.
          error: invalid_gtin
      properties:
        error:
          description: Short error code, e.g. ``invalid_gtin``.
          title: Error
          type: string
        detail:
          description: Human-readable error description.
          title: Detail
          type: string
      required:
        - error
        - detail
      title: VerificationErrorOut
      type: object
    VerificationStatusEnum:
      description: >-
        Public verification status emitted by the ``gs1:verificationService``
        API.


        Mirrors :class:`apps.resolver.models.SerialVerificationStatus` minus

        ``unverified`` -- the unverified internal state collapses to

        ``authentic`` on the public contract until Phase 3 (C-2292) ships

        positive-authentication rules. The four published values are exactly

        those that map to a meaningful consumer-facing recommendation:


        * ``authentic`` -> recommendation ``proceed``

        * ``suspect`` / ``counterfeit_suspected`` / ``serialization_error``
          -> recommendation ``flag_for_review``
      enum:
        - authentic
        - suspect
        - counterfeit_suspected
        - serialization_error
      title: VerificationStatusEnum
      type: string
    VerificationRecommendationEnum:
      description: |-
        Recommended action returned to the scanner.

        The two-value enum is intentionally narrow -- callers (consumer
        apps, retailer POS, 2D-cert tools) translate it directly into a
        UI/UX decision (let the user proceed vs surface a review/flag
        prompt). Phase 3 may introduce richer recommendations
        (``hold_for_inspection``, ``contact_brand``, etc.); those will land
        as additive enum values without breaking existing clients.
      enum:
        - proceed
        - flag_for_review
      title: VerificationRecommendationEnum
      type: string
    ScanHistoryOut:
      description: Aggregate scan counters for the authenticated verification response.
      examples:
        - consumerScans: 3
          distinctRetailLocations: 2
          firstSeen: '2026-04-01T10:00:00Z'
          retailerScans: 2
          totalScans: 5
      properties:
        totalScans:
          description: Sum of every scan-context counter for this (gtin, serial).
          minimum: 0
          title: Totalscans
          type: integer
        retailerScans:
          description: >-
            Number of scans authenticated as a retailer-system caller
            (X-API-Key).
          minimum: 0
          title: Retailerscans
          type: integer
        consumerScans:
          description: Number of scans classified as consumer (browser/mobile, no API key).
          minimum: 0
          title: Consumerscans
          type: integer
        distinctRetailLocations:
          description: >-
            Count of unique retail locations (GLN or IP-derived geography) that
            have scanned this serial. Drives the duplicate-retail-scan anomaly
            rule when ``>= 2``.
          minimum: 0
          title: Distinctretaillocations
          type: integer
        firstSeen:
          description: UTC ISO-8601 timestamp of the earliest observed scan of this serial.
          format: date-time
          title: Firstseen
          type: string
      required:
        - totalScans
        - retailerScans
        - consumerScans
        - distinctRetailLocations
        - firstSeen
      title: ScanHistoryOut
      type: object
    AnomalyOut:
      description: A single anomaly entry on the authenticated verification response.
      examples:
        - 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
      properties:
        type:
          description: >-
            Stable rule identifier. Phase 1 ships ``duplicate_retail_scan``;
            Phase 3 (C-2292) layers on excessive scans, geographic
            impossibility, post-sale re-entry, serialization errors, and
            mass-scan rules.
          title: Type
          type: string
        description:
          description: Human-readable explanation of what the rule observed.
          title: Description
          type: string
        priorEvent:
          anyOf:
            - $ref: '#/components/schemas/PriorEventOut'
            - type: 'null'
          description: >-
            Earlier scan event that contributed to the anomaly, when applicable.
            ``null`` when the rule does not produce a prior-event hint.
      required:
        - type
        - description
      title: AnomalyOut
      type: object
    PriorEventOut:
      description: Minimal description of a prior retailer scan, for anomaly context.
      examples:
        - location: 49.2827,-123.1207
          retailer: RetailerA
          scannedAt: '2026-04-10T14:30:00Z'
      properties:
        location:
          description: >-
            Best-effort human-readable location string for the prior scan.
            Currently a coordinate pair (``'lat,lon'``) when only the IP-derived
            geo is available; reverse-geocoded city names land in a follow-up.
            Empty string when no location was captured.
          title: Location
          type: string
        retailer:
          description: >-
            Retailer organization name when the prior scan was authenticated via
            a retailer API key. Empty string when the prior scan was
            unauthenticated or the retailer org has been deleted.
          title: Retailer
          type: string
        scannedAt:
          description: UTC ISO-8601 timestamp of the prior scan.
          format: date-time
          title: Scannedat
          type: string
      required:
        - location
        - retailer
        - scannedAt
      title: PriorEventOut
      type: object

````