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
| Weg | Anfrage | Antwort |
|---|---|---|
| Dem Link folgen (empfohlen) | Der Link mit ?linkType=describedby | 307 zum öffentlichen JSON |
| Das Linkset abfragen | Der Link mit ?linkType=all oder mit Accept: application/json ohne linkType | 200 mit application/linkset+json; der Eintrag describedby ist das JSON |
| Über GTIN und Seriennummer nachschlagen | getPublicPassportByProductId | 200 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 -sS -i -H "Accept: application/json" \
"https://batterypass.example.com/01/04012345000023/21/SV2027-000184?linkType=describedby"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: AcceptHTTP-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:
{
"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 -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
| Antwort | Bedeutung |
|---|---|
404 vom Resolver | Kein Pass für diese GTIN und Seriennummer: ein Problem not_found für JSON-Clients, eine kurze Seite für Browser. |
200 ohne document | Den Pass gibt es, aber er ist gerade nicht öffentlich; status nennt den Grund. |
400 validation_error | getPublicPassportByProductId mit einem Wert, der nicht {gtin}:{serial} mit gültiger GTIN ist. |
429 rate_limited | Mehr 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.
Fehler und Wiederholungen
Entscheiden Sie, welche Antworten Sie wiederholen und welche Sie beheben, warten Sie mit Backoff und Jitter, beachten Sie Retry-After und protokollieren Sie die request_id.
Schlüssel sicher verwalten
Wo Schlüssel hingehören, welcher Scope für welches System, wie Sie ohne Ausfall wechseln und was zu tun ist, wenn ein Schlüssel bekannt wird.