# Schnellstart

> In wenigen Minuten einen API-Schlüssel erstellen, den ersten Aufruf senden und einen Pass lesen.

Sie brauchen:

- die BatteryPass-Adresse Ihrer Organisation, zum Beispiel `https://batterypass.example.com`;
- die Rolle Eigentümer in dieser Organisation oder einen Eigentümer, der den Schlüssel für Sie erstellt;
- `curl` und `jq`, oder Node.js 20+ (die TypeScript-Beispiele sind ES-Module, z. B. `npx tsx quickstart.mts`),
  Python 3 mit `requests` oder Go.

1. **API-Schlüssel erstellen**

   Ein Eigentümer meldet sich im BatteryPass-Portal an, öffnet **Einstellungen → API-Schlüssel** und wählt
   **API-Schlüssel erstellen**. Geben Sie dem Schlüssel einen Namen, der das nutzende System nennt, und wählen
   Sie den Scope **Lesen** (`read`). Der Scope **Zustandsdaten melden** (`condition`) ist nur für Systeme
   gedacht, die Messwerte und Lebenszyklusereignisse melden.

   Das Geheimnis beginnt mit `bp_live_` und wird **nur ein einziges Mal** angezeigt. Legen Sie es sofort in
   Ihrem Secret-Speicher ab; später lässt es sich nicht mehr anzeigen. Schlüssel werden nur im Portal verwaltet,
   nicht über die API.

2. **Zwei Umgebungsvariablen setzen**

   ```bash title="Shell"
   export BATTERYPASS_URL="https://batterypass.example.com"
   export BATTERYPASS_API_KEY="bp_live_..."
   ```

   `BATTERYPASS_URL` ist die BatteryPass-Adresse Ihrer Organisation ohne abschließenden Schrägstrich. Alle
   Beispiele dieser Dokumentation lesen diese beiden Variablen. Mehr dazu unter
   [Umgebungen und Basis-URL](https://passport.kabin.sh/docs/de/environments).

3. **Den ersten Aufruf senden**

   Listen Sie mit [`listBatteryModels`](https://passport.kabin.sh/docs/de/api-reference/catalog/list-battery-models) die ersten fünf
   Batteriemodelle Ihrer Organisation auf:

   ```bash title="cURL"
   curl -sS "$BATTERYPASS_URL/api/v1/battery-models?limit=5" \
     -H "Authorization: Bearer $BATTERYPASS_API_KEY"
   ```

   ```json title="200"
   {
     "items": [
       {
         "id": "0192f0a1-6c2e-7d4b-9a10-3e5f7b2c8d41",
         "name": "SolarVolt Home 10",
         "sku": "SV-H10",
         "gtin": "04012345000023",
         "category": "stationary",
         "archived_at": null,
         "row_version": 3,
         "created_at": "2026-01-08T09:12:44Z",
         "updated_at": "2026-03-02T15:40:10Z"
       }
     ],
     "next_cursor": null
   }
   ```

   Ein `401` bedeutet, dass der Header fehlt oder der Schlüssel unbekannt oder widerrufen ist; ein `403`, dass
   dem Schlüssel der Scope `read` fehlt. Siehe [Authentifizierung](https://passport.kabin.sh/docs/de/authentication) und
   [Fehler](https://passport.kabin.sh/docs/de/errors).

4. **Batteriemodelle im Code auflisten**

   ```ts title="TypeScript"
   const res = await fetch(`${process.env.BATTERYPASS_URL}/api/v1/battery-models?limit=50`, {
     headers: { Authorization: `Bearer ${process.env.BATTERYPASS_API_KEY}` },
   });
   if (!res.ok) throw new Error(`listBatteryModels: ${res.status} ${await res.text()}`);

   const page: { items: { id: string; name: string; gtin: string | null }[]; next_cursor: string | null } =
     await res.json();
   for (const model of page.items) console.log(model.name, model.gtin ?? '(keine GTIN)', model.id);
   if (page.next_cursor) console.log('Weitere Modelle auf der nächsten Seite.');
   ```

   ```python title="Python"
   import os

   import requests

   res = requests.get(
       f"{os.environ['BATTERYPASS_URL']}/api/v1/battery-models",
       headers={"Authorization": f"Bearer {os.environ['BATTERYPASS_API_KEY']}"},
       params={"limit": 50},
       timeout=30,
   )
   res.raise_for_status()
   page = res.json()
   for model in page["items"]:
       print(model["name"], model["gtin"] or "(keine GTIN)", model["id"])
   if page["next_cursor"]:
       print("Weitere Modelle auf der nächsten Seite.")
   ```

   ```go title="Go"
   package main

   import (
   	"encoding/json"
   	"fmt"
   	"log"
   	"net/http"
   	"os"
   	"time"
   )

   func main() {
   	req, err := http.NewRequest(http.MethodGet, os.Getenv("BATTERYPASS_URL")+"/api/v1/battery-models?limit=50", nil)
   	if err != nil {
   		log.Fatal(err)
   	}
   	req.Header.Set("Authorization", "Bearer "+os.Getenv("BATTERYPASS_API_KEY"))
   	res, err := (&http.Client{Timeout: 30 * time.Second}).Do(req)
   	if err != nil {
   		log.Fatal(err)
   	}
   	defer res.Body.Close()
   	if res.StatusCode != http.StatusOK {
   		log.Fatalf("listBatteryModels: %s", res.Status)
   	}
   	var page struct {
   		Items []struct {
   			ID   string  `json:"id"`
   			Name string  `json:"name"`
   			GTIN *string `json:"gtin"`
   		} `json:"items"`
   		NextCursor *string `json:"next_cursor"`
   	}
   	if err := json.NewDecoder(res.Body).Decode(&page); err != nil {
   		log.Fatal(err)
   	}
   	for _, m := range page.Items {
   		gtin := "(keine GTIN)"
   		if m.GTIN != nil {
   			gtin = *m.GTIN
   		}
   		fmt.Println(m.Name, gtin, m.ID)
   	}
   	if page.NextCursor != nil {
   		fmt.Println("Weitere Modelle auf der nächsten Seite.")
   	}
   }
   ```

   Listen liefern höchstens `limit` Einträge und einen `next_cursor`; wie Sie alle Seiten lesen, zeigt
   [Paginierung](https://passport.kabin.sh/docs/de/pagination).

5. **Den Pass einer Einheit lesen**

   Suchen Sie mit [`listBatteries`](https://passport.kabin.sh/docs/de/api-reference/catalog/list-batteries) eine Einheit mit
   veröffentlichtem Pass und lesen Sie dann ihren Pass mit
   [`getPassport`](https://passport.kabin.sh/docs/de/api-reference/passports/get-passport):

   ```bash title="cURL"
   BATTERY_ID=$(curl -sS "$BATTERYPASS_URL/api/v1/batteries?passport_status=published&limit=1" \
     -H "Authorization: Bearer $BATTERYPASS_API_KEY" | jq -r '.items[0].id')

   curl -sS "$BATTERYPASS_URL/api/v1/batteries/$BATTERY_ID/passport" \
     -H "Authorization: Bearer $BATTERYPASS_API_KEY"
   ```

   ```ts title="TypeScript"
   const base = `${process.env.BATTERYPASS_URL}/api/v1`;
   const headers = { Authorization: `Bearer ${process.env.BATTERYPASS_API_KEY}` };

   const units = await fetch(`${base}/batteries?passport_status=published&limit=1`, { headers });
   if (!units.ok) throw new Error(`listBatteries: ${units.status}`);
   const { items }: { items: { id: string }[] } = await units.json();
   if (items.length === 0) throw new Error('Noch keine Einheit mit veröffentlichtem Pass.');

   const res = await fetch(`${base}/batteries/${items[0].id}/passport`, { headers });
   if (!res.ok) throw new Error(`getPassport: ${res.status}`);
   const passport = await res.json();
   console.log(passport.passport_id, passport.status, passport.public_url);
   ```

   ```python title="Python"
   import os

   import requests

   base = f"{os.environ['BATTERYPASS_URL']}/api/v1"
   headers = {"Authorization": f"Bearer {os.environ['BATTERYPASS_API_KEY']}"}

   units = requests.get(f"{base}/batteries", headers=headers,
                        params={"passport_status": "published", "limit": 1}, timeout=30)
   units.raise_for_status()
   items = units.json()["items"]
   if not items:
       raise SystemExit("Noch keine Einheit mit veröffentlichtem Pass.")

   res = requests.get(f"{base}/batteries/{items[0]['id']}/passport", headers=headers, timeout=30)
   res.raise_for_status()
   passport = res.json()
   print(passport["passport_id"], passport["status"], passport.get("public_url"))
   ```

   ```go title="Go"
   package main

   import (
   	"encoding/json"
   	"fmt"
   	"log"
   	"net/http"
   	"os"
   	"time"
   )

   var client = &http.Client{Timeout: 30 * time.Second}

   // get sendet ein autorisiertes GET an die API und dekodiert die JSON-Antwort in out.
   func get(path string, out any) error {
   	req, err := http.NewRequest(http.MethodGet, os.Getenv("BATTERYPASS_URL")+"/api/v1"+path, nil)
   	if err != nil {
   		return err
   	}
   	req.Header.Set("Authorization", "Bearer "+os.Getenv("BATTERYPASS_API_KEY"))
   	res, err := client.Do(req)
   	if err != nil {
   		return err
   	}
   	defer res.Body.Close()
   	if res.StatusCode != http.StatusOK {
   		return fmt.Errorf("GET %s: %s", path, res.Status)
   	}
   	return json.NewDecoder(res.Body).Decode(out)
   }

   func main() {
   	var units struct {
   		Items []struct {
   			ID string `json:"id"`
   		} `json:"items"`
   	}
   	if err := get("/batteries?passport_status=published&limit=1", &units); err != nil {
   		log.Fatal(err)
   	}
   	if len(units.Items) == 0 {
   		log.Fatal("Noch keine Einheit mit veröffentlichtem Pass.")
   	}
   	var passport struct {
   		PassportID string `json:"passport_id"`
   		Status     string `json:"status"`
   		PublicURL  string `json:"public_url"`
   	}
   	if err := get("/batteries/"+units.Items[0].ID+"/passport", &passport); err != nil {
   		log.Fatal(err)
   	}
   	fmt.Println(passport.PassportID, passport.Status, passport.PublicURL)
   }
   ```

   ```json title="200"
   {
     "battery_id": "0192f0a3-1b7c-7e2a-8f45-6a9d0c3e5b12",
     "status": "published",
     "serial_number": "SV2027-000001",
     "gtin": "04012345000023",
     "passport_id": "urn:dpp:0192f3c4-7e1a-7b2c-9d3e-5f6a7b8c9d0e",
     "gs1_digital_link": "https://batterypass.example.com/01/04012345000023/21/SV2027-000001",
     "public_url": "https://batterypass.example.com/pass/0192f3c4-7e1a-7b2c-9d3e-5f6a7b8c9d0e",
     "first_published_at": "2026-03-10T08:30:00Z",
     "identifiers_locked_at": "2026-03-10T08:30:00Z",
     "row_version": 4
   }
   ```

   Die Antwort enthält außerdem `current_version` mit Versionsnummer, Hashes und Signaturschlüssel der aktuellen
   Version. Eine Einheit, die nie veröffentlicht wurde, hat einen Pass im Status `draft` ohne `passport_id`.

6. **Den öffentlichen Pass ohne Schlüssel lesen**

   Die öffentliche Stufe eines veröffentlichten Passes braucht keinen Schlüssel.
   [`getPublicPassport`](https://passport.kabin.sh/docs/de/api-reference/public/get-public-passport) erwartet den UUID-Teil von
   `passport_id`, ohne das Präfix `urn:dpp:`:

   ```bash title="cURL"
   PASSPORT_UUID=$(curl -sS "$BATTERYPASS_URL/api/v1/batteries/$BATTERY_ID/passport" \
     -H "Authorization: Bearer $BATTERYPASS_API_KEY" | jq -r '.passport_id | sub("^urn:dpp:"; "")')

   curl -sS "$BATTERYPASS_URL/api/v1/public/passports/$PASSPORT_UUID" | jq '{passport_id, status, links}'
   ```

   ```ts title="TypeScript"
   // passport_id aus getPassport
   const passportId = 'urn:dpp:0192f3c4-7e1a-7b2c-9d3e-5f6a7b8c9d0e';
   const uuid = passportId.replace(/^urn:dpp:/, '');

   const res = await fetch(`${process.env.BATTERYPASS_URL}/api/v1/public/passports/${uuid}`);
   if (!res.ok) throw new Error(`getPublicPassport: ${res.status}`);
   const pub = await res.json();
   console.log(pub.status, pub.document?.model_name, pub.links.page);
   ```

   ```python title="Python"
   import os

   import requests

   passport_id = "urn:dpp:0192f3c4-7e1a-7b2c-9d3e-5f6a7b8c9d0e"  # aus getPassport
   uuid = passport_id.removeprefix("urn:dpp:")

   res = requests.get(f"{os.environ['BATTERYPASS_URL']}/api/v1/public/passports/{uuid}", timeout=30)
   res.raise_for_status()
   pub = res.json()
   print(pub["status"], pub.get("document", {}).get("model_name"), pub["links"]["page"])
   ```

   ```go title="Go"
   package main

   import (
   	"encoding/json"
   	"fmt"
   	"log"
   	"net/http"
   	"os"
   	"strings"
   	"time"
   )

   func main() {
   	passportID := "urn:dpp:0192f3c4-7e1a-7b2c-9d3e-5f6a7b8c9d0e" // aus getPassport
   	uuid := strings.TrimPrefix(passportID, "urn:dpp:")

   	client := &http.Client{Timeout: 30 * time.Second}
   	res, err := client.Get(os.Getenv("BATTERYPASS_URL") + "/api/v1/public/passports/" + uuid)
   	if err != nil {
   		log.Fatal(err)
   	}
   	defer res.Body.Close()
   	if res.StatusCode != http.StatusOK {
   		log.Fatalf("getPublicPassport: %s", res.Status)
   	}
   	var pub struct {
   		Status   string `json:"status"`
   		Document *struct {
   			ModelName string `json:"model_name"`
   		} `json:"document"`
   		Links struct {
   			Page string `json:"page"`
   		} `json:"links"`
   	}
   	if err := json.NewDecoder(res.Body).Decode(&pub); err != nil {
   		log.Fatal(err)
   	}
   	model := "(kein Inhalt)"
   	if pub.Document != nil {
   		model = pub.Document.ModelName
   	}
   	fmt.Println(pub.Status, model, pub.Links.Page)
   }
   ```

   `document` ist die signierte öffentliche Projektion der aktuellen Version; `verification` enthält ihre
   Ed25519-Signatur. Pässe, die nicht öffentlich sind (zum Beispiel `withdrawn`), antworten mit ihrem Status und
   ohne `document`. Öffentliche Abrufe werden fünf Minuten zwischengespeichert und sind auf 300 Anfragen pro
   Minute und Client-Adresse begrenzt.

## Nächste Schritte

[**Authentifizierung**](https://passport.kabin.sh/docs/de/authentication): Scopes, Rechte eines Schlüssels, Rotation und 401 oder 403.

[**Fehler**](https://passport.kabin.sh/docs/de/errors): Problem Details, Codes und die Request-ID.

[**Zustandsdaten senden**](https://passport.kabin.sh/docs/de/guides/push-condition-data): Messwerte aus einem Batteriemanagementsystem melden.

[**Signatur prüfen**](https://passport.kabin.sh/docs/de/guides/verify-signature): Einen Pass offline mit den öffentlichen Schlüsseln prüfen.
