# GS1 Digital Link and resolver

> The link on every QR code, and how the resolver sends browsers to the public page and machines to the public JSON.

Every passport has one web address that identifies the battery: a GS1 Digital Link. It is what the QR code on
the battery carries. The same address serves people and machines: the resolver behind it decides what to
return from the request.

## The link

```text
https://batterypass.example.com/01/04012345000023/21/SV2027-000184
```

| Part                              | Meaning                                                                                                             |
| --------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| `https://batterypass.example.com` | The resolver's origin: your BatteryPass address, or a separate resolver hostname your operator configured           |
| `/01/04012345000023`              | GS1 application identifier `01`: the model's GTIN, always with 14 digits                                            |
| `/21/SV2027-000184`               | GS1 application identifier `21`: the unit's serial number; a `/` in a serial number is sent percent-encoded (`%2F`) |

The link is in `gs1_digital_link` of [`getPassport`](https://passport.kabin.sh/docs/en/api-reference/passports/get-passport) and is fixed
once the identifiers are locked. [`getPassportQr`](https://passport.kabin.sh/docs/en/api-reference/passports/get-passport-qr) renders
exactly this link as PNG or SVG (error correction level M, a quiet zone of four modules). Always take the link
from the API rather than building it yourself.

## What the resolver returns

The resolver lives at the root of the origin (`/01/…/21/…`), not under `/api/v1`, and needs no key.

| Request                                                                                                                | Answer                                                                   |
| ---------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ |
| A browser (`Accept: text/html`, or no JSON preference)                                                                 | `307` to the public passport page `/pass/{passportId}`                   |
| `?linkType=describedby` (or `gs1:describedby`)                                                                         | `307` to the public JSON `/api/v1/public/passports/{passportId}`         |
| `?linkType=all`, or `Accept: application/json`, `application/linkset+json` or `application/ld+json` without `linkType` | `200` with a linkset (`application/linkset+json`)                        |
| An unknown GTIN and serial number                                                                                      | `404`: a short page for browsers, a Problem `not_found` for JSON clients |
| Too many requests from one address                                                                                     | `429 rate_limited` with `Retry-After`                                    |

Every answer for a known passport also carries the links in a `Link` header, with the relations
`https://gs1.org/voc/pip` (the page), `describedby` (the public JSON) and `linkset`. Answers are cacheable for
five minutes and vary by `Accept`.

A passport whose identifiers are locked but that is not published yet, or one that was withdrawn, still
resolves: its page explains the state, and the public JSON returns the status without content.

## Following the link from code

```bash title="cURL"
# The public JSON of the passport behind a QR code
curl -sL "https://batterypass.example.com/01/04012345000023/21/SV2027-000184?linkType=describedby"

# The linkset
curl -s -H "Accept: application/linkset+json" \
  "https://batterypass.example.com/01/04012345000023/21/SV2027-000184"
```

The linkset names the page and the public JSON:

```json title="Linkset"
{
  "linkset": [
    {
      "anchor": "https://batterypass.example.com/01/04012345000023/21/SV2027-000184",
      "https://gs1.org/voc/pip": [
        { "href": "https://batterypass.example.com/pass/0192f0c4-7b1e-7c3a-9d2e-5a8b6c4d3e21", "type": "text/html", "title": "Battery passport" }
      ],
      "describedby": [
        { "href": "https://batterypass.example.com/api/v1/public/passports/0192f0c4-7b1e-7c3a-9d2e-5a8b6c4d3e21", "type": "application/json", "title": "Public passport (JSON)" }
      ]
    }
  ]
}
```

If you already know GTIN and serial number, you can skip the resolver and call
[`getPublicPassportByProductId`](https://passport.kabin.sh/docs/en/api-reference/public/get-public-passport-by-product-id) with
`{gtin}:{serial}`, for example `04012345000023:SV2027-000184`. The GTIN may have 8, 12, 13 or 14 digits there.

> [!NOTE]
> The resolver, the public passport reads and the public documents share one limit of 300 requests per minute per
> client address. A scanner app that resolves many codes should respect the cache headers; public passport
> reads may be cached for five minutes.

## Related pages

[**Resolve a Digital Link**](https://passport.kabin.sh/docs/en/guides/resolve-digital-link): Turn a scanned code into the public passport data.

[**QR labels for a batch**](https://passport.kabin.sh/docs/en/recipes/qr-labels-for-a-batch): Fetch the QR codes for the units of a batch.
