Export passports
Download the BatteryPass JSON, the PDF and the QR code of a passport version, and the units of your organisation as a spreadsheet.
All exports are plain GET requests with a key that has the scope read. They come back as files with a
Content-Disposition: attachment header that carries a ready-made file name.
| Export | Operation | Content type | File name |
|---|---|---|---|
| BatteryPass v2.0 JSON of a version | exportPassportJson | application/json | {gtin}-{serial}-batterypass-v2.json |
| PDF of a version | exportPassportPdf | application/pdf | {gtin}-{serial}-v{versionNo}-{lang}.pdf |
| QR code of the unit | getPassportQr | image/png or image/svg+xml | qr-{gtin}-{serial}.png or .svg |
| Units as a spreadsheet | exportUnits | XLSX or text/csv | units-{YYYYMMDD}.xlsx or .csv |
A / in a serial number becomes _ in file names.
Passport exports step by step
Find the version
The JSON and the PDF belong to a published version. getPassport
returns the current one in current_version.version_no;
listPassportVersions lists all versions, newest
first.
curl -sS "$BATTERYPASS_URL/api/v1/batteries/0192a3f0-5b7c-7d21-8e4f-6a2b9c1d3e5f/passport" \
-H "Authorization: Bearer $BATTERYPASS_API_KEY" | jq '{status, passport_id, version: .current_version.version_no}'A unit that was never published has no version: the version exports answer 404 not_found.
Download the files
BATTERY_ID=0192a3f0-5b7c-7d21-8e4f-6a2b9c1d3e5f
# -O -J saves under the name from Content-Disposition
curl -sS -OJ -H "Authorization: Bearer $BATTERYPASS_API_KEY" \
"$BATTERYPASS_URL/api/v1/batteries/$BATTERY_ID/passport/versions/3/batterypass-v2"
curl -sS -OJ -H "Authorization: Bearer $BATTERYPASS_API_KEY" \
"$BATTERYPASS_URL/api/v1/batteries/$BATTERY_ID/passport/versions/3/pdf?lang=en"
curl -sS -OJ -H "Authorization: Bearer $BATTERYPASS_API_KEY" \
"$BATTERYPASS_URL/api/v1/batteries/$BATTERY_ID/passport/qr?format=svg"In code, read the version first and take the file name from Content-Disposition:
// exports.mts: npx tsx exports.mts <battery-id>
import { writeFile } from 'node:fs/promises';
import { basename } from 'node:path';
const BASE = `${process.env.BATTERYPASS_URL}/api/v1`;
const headers = { Authorization: `Bearer ${process.env.BATTERYPASS_API_KEY}` };
const batteryId = process.argv[2];
async function download(path: string) {
const res = await fetch(BASE + path, { headers });
if (!res.ok) throw new Error(`${path}: ${res.status} ${(await res.json()).code}`);
// Content-Disposition: attachment; filename=04012345000023-SV2027-000184-v3-en.pdf
const match = /filename="?([^";]+)"?/.exec(res.headers.get('Content-Disposition') ?? '');
const file = basename(match?.[1] ?? 'download');
await writeFile(file, Buffer.from(await res.arrayBuffer()));
console.log(file, res.headers.get('Content-Type'));
}
const passportRes = await fetch(`${BASE}/batteries/${batteryId}/passport`, { headers });
const passport = await passportRes.json();
const versionNo = passport.current_version?.version_no;
if (!versionNo) throw new Error(`passport is ${passport.status}: no published version yet`);
await download(`/batteries/${batteryId}/passport/versions/${versionNo}/batterypass-v2`);
await download(`/batteries/${batteryId}/passport/versions/${versionNo}/pdf?lang=en`);
await download(`/batteries/${batteryId}/passport/qr?format=svg`);BatteryPass JSON
exportPassportJson returns the export that was built and stored when the version was published, in the
format of the BatteryPass-Ready Data Model v2.0. It contains every access tier, including restricted values, so
treat the file as confidential. Whether it passed the data model's JSON schema is advisory and does not block
publication: the version summary carries batterypass_v2_valid and batterypass_v2_issue_count, and
getPassportVersion the full report in
batterypass_v2.
exportPassportPdf renders a version with every tier (restricted rows tagged with their tier), the signature and
the QR code. lang is de (the default) or en. Like the JSON, the PDF contains restricted data.
QR code
getPassportQr renders the unit's gs1_digital_link (error correction level M, a quiet zone of four modules).
formatstringquerydefault: pngpng or svg. SVG scales without loss and suits printing.sizeintegerquerydefault: 512The QR code exists once the identifiers are locked, before or at the first publication. Before that, the answer
is 409 conflict. The link inside it never changes afterwards, so a printed label stays valid for later
versions. To label many units at once, see the recipe QR labels for a batch.
Units as a spreadsheet
exportUnits returns the units that match the filters, with the columns of the spreadsheet import: model,
version, the unit's own fields and every unit-level field that has a value. A member can edit the file and
import it again in the portal.
formatstringquerydefault: xlsxxlsx or csv. CSV is UTF-8 with a byte order mark, comma-separated, with CRLF line ends.battery_model_iduuidquerymodel_version_iduuidquerypassport_statusstringquerypublished.serial_prefixstringqueryimport_iduuidquerycurl -sS -OJ -H "Authorization: Bearer $BATTERYPASS_API_KEY" \
"$BATTERYPASS_URL/api/v1/batteries/export?format=csv&passport_status=published&serial_prefix=SV2027-"At most 10 000 units fit in one file. With more, the answer is 400 validation_error on filter
(out_of_range): narrow the filters, for example by model version or serial number prefix. In CSV files, a cell
that starts with =, +, @, or with - but is not a number, gets a leading ' so spreadsheet programs do
not run it as a formula; the import removes it again.
Errors
Status and code | Typical cause |
|---|---|
400 validation_error | size outside 128 to 2048; an unknown format or lang; more than 10 000 units in exportUnits |
401 unauthenticated | Key missing, unknown or revoked |
403 forbidden | The key lacks the scope read |
404 not_found | Unknown unit, or no version with this number |
409 conflict | getPassportQr before the identifiers are locked |
429 rate_limited | Too many requests; wait for Retry-After |
Record lifecycle events
Report inspections, repairs and other events in the life of a unit with createLifecycleEvent, and read the lifecycle with listLifecycleEvents.
Sync units
Copy the units of your organisation into an ERP, MES or database with cursor paging, resume after an interruption and detect what changed.