Zum Inhalt springen

QR-Code oder GS1 Digital Link auflösen

Vom gescannten GS1 Digital Link zum öffentlichen Pass-JSON, über den Resolver oder über GTIN und Seriennummer.

Der QR-Code auf einer Batterie enthält einen GS1 Digital Link wie https://batterypass.example.com/01/04012345000023/21/SV2027-000184. Ein Browser, der ihn öffnet, landet auf der öffentlichen Passseite. Eine Scanner-App, das System eines Recyclers oder ein Marktplatz will dagegen meist die Daten: Diese Anleitung holt das öffentliche Pass-JSON über den Link. Ein Schlüssel ist nicht nötig. Den Hintergrund erklärt GS1 Digital Link und Resolver.

Drei Wege zum JSON

WegAnfrageAntwort
Dem Link folgen (empfohlen)Der Link mit ?linkType=describedby307 zum öffentlichen JSON
Das Linkset abfragenDer Link mit ?linkType=all oder mit Accept: application/json ohne linkType200 mit application/linkset+json; der Eintrag describedby ist das JSON
Über GTIN und Seriennummer nachschlagengetPublicPassportByProductId200 mit dem öffentlichen JSON

Dem Link zu folgen funktioniert für jede BatteryPass-Installation und jeden Resolver-Hostnamen, denn der Link sagt selbst, wohin es geht. Zum Nachschlagen müssen Sie die BatteryPass-Adresse kennen, die den Pass führt.

Schritt für Schritt

Link zerlegen

Ein Digital Link von BatteryPass hat den Pfad /01/{gtin}/21/{serial}: den Application Identifier 01 mit der GTIN des Modells (14 Ziffern) und 21 mit der Seriennummer der Einheit. Seriennummern bestehen aus Buchstaben, Ziffern, ., - und /; ein / reist prozentkodiert als %2F. Dekodieren Sie das Segment deshalb erst nach dem Zerlegen des Pfads. Übernehmen Sie den Ursprung des Links unverändert: Er kann ein eigener Resolver-Hostname sein und nicht die Adresse der API.

JSON anfordern

Hängen Sie linkType=describedby an und senden Sie Accept: application/json, damit ein unbekannter Pass als JSON-Problem statt als HTML-Seite zurückkommt:

cURL
curl -sS -i -H "Accept: application/json" \
  "https://batterypass.example.com/01/04012345000023/21/SV2027-000184?linkType=describedby"
307
HTTP/1.1 307 Temporary Redirect
Location: https://batterypass.example.com/api/v1/public/passports/0192f0c4-7d1e-7a3b-9c55-2f1e8d4a6b10
Link: <https://batterypass.example.com/pass/0192f0c4-7d1e-7a3b-9c55-2f1e8d4a6b10>; rel="https://gs1.org/voc/pip"; type="text/html", <https://batterypass.example.com/api/v1/public/passports/0192f0c4-7d1e-7a3b-9c55-2f1e8d4a6b10>; rel="describedby"; type="application/json", <https://batterypass.example.com/01/04012345000023/21/SV2027-000184?linkType=all>; rel="linkset"; type="application/linkset+json"
Cache-Control: public, max-age=300
Vary: Accept

HTTP-Clients folgen dem 307 von selbst (curl -L, fetch, requests, http.Client in Go). Das Ziel ist getPublicPassport.

Pass lesen

Das JSON enthält passport_id (urn:dpp:{uuid}), status, links und, solange der Pass öffentlich ist, das signierte document mit seiner verification. Ein Pass mit gesperrten Kennungen, der noch nicht veröffentlicht ist (qr_locked), oder einer im Status suspended, withdrawn oder deleted_recycled lässt sich weiterhin auflösen, sein JSON enthält aber nur den Status, ohne document. Ob die Daten echt sind, prüfen Sie mit der Signatur, wie in Passsignatur offline prüfen gezeigt.

Vollständiger Code

Jedes Programm erwartet den gescannten Link als Argument und gibt GTIN, Seriennummer, Pass-ID, Status und die URL des JSON aus.

// resolve.mts: npx tsx resolve.mts <digital-link>
const link = new URL(process.argv[2]); // e.g. https://batterypass.example.com/01/04012345000023/21/SV2027-000184

const match = /\/01\/(\d{8,14})\/21\/([^/]+)$/.exec(link.pathname);
if (!match) throw new Error('not a GS1 Digital Link with GTIN and serial number');
const gtin = match[1];
const serial = decodeURIComponent(match[2]); // a "/" in the serial number arrives as %2F

// linkType=describedby: the resolver answers 307 with the public JSON as Location; fetch follows it.
link.searchParams.set('linkType', 'describedby');
const res = await fetch(link, { headers: { Accept: 'application/json' } });
const body = await res.json();
if (!res.ok) throw new Error(`${res.status} ${body.code}: ${body.detail}`);

console.log(gtin, serial, body.passport_id, body.status, res.url);

Das Linkset

Mit ?linkType=all oder mit Accept: application/json (auch application/linkset+json oder application/ld+json) ohne linkType antwortet der Resolver mit 200 und allen Links des Passes:

200
{
  "linkset": [
    {
      "anchor": "https://batterypass.example.com/01/04012345000023/21/SV2027-000184",
      "https://gs1.org/voc/pip": [
        { "href": "https://batterypass.example.com/pass/0192f0c4-7d1e-7a3b-9c55-2f1e8d4a6b10", "type": "text/html", "title": "Battery passport" }
      ],
      "describedby": [
        { "href": "https://batterypass.example.com/api/v1/public/passports/0192f0c4-7d1e-7a3b-9c55-2f1e8d4a6b10", "type": "application/json", "title": "Public passport (JSON)" }
      ]
    }
  ]
}

Über GTIN und Seriennummer nachschlagen

Kennen Sie die BatteryPass-Adresse und haben GTIN und Seriennummer aus einer anderen Quelle (Etikett, Lieferschein, ERP-Datensatz), nutzen Sie getPublicPassportByProductId. Der Pfadparameter lautet {gtin}:{serial}. Die GTIN darf 8, 12, 13 oder 14 Ziffern haben und braucht eine gültige Prüfziffer; die Seriennummer senden Sie URL-kodiert (Prozentkodierung).

cURL
curl -sS "$BATTERYPASS_URL/api/v1/public/dpps/by-product-id/04012345000023:SV2027-000184"

Die Antwort ist dasselbe öffentliche JSON wie oben. Ein fehlerhafter Wert ergibt 400 validation_error auf productId (invalid_format), ein unbekanntes Paar 404 not_found.

Fehler und Limits

AntwortBedeutung
404 vom ResolverKein Pass für diese GTIN und Seriennummer: ein Problem not_found für JSON-Clients, eine kurze Seite für Browser.
200 ohne documentDen Pass gibt es, aber er ist gerade nicht öffentlich; status nennt den Grund.
400 validation_errorgetPublicPassportByProductId mit einem Wert, der nicht {gtin}:{serial} mit gültiger GTIN ist.
429 rate_limitedMehr als 300 öffentliche Anfragen pro Minute von Ihrer Adresse (Resolver und öffentliche Abrufe zusammen); Retry-After abwarten.

Antworten des Resolvers und das aktuelle öffentliche JSON dürfen fünf Minuten zwischengespeichert werden. Wenn Sie viele Codes scannen, speichern Sie die Ergebnisse auch auf Ihrer Seite zwischen.

Auf dieser Seite