Skip to content

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.

ExportOperationContent typeFile name
BatteryPass v2.0 JSON of a versionexportPassportJsonapplication/json{gtin}-{serial}-batterypass-v2.json
PDF of a versionexportPassportPdfapplication/pdf{gtin}-{serial}-v{versionNo}-{lang}.pdf
QR code of the unitgetPassportQrimage/png or image/svg+xmlqr-{gtin}-{serial}.png or .svg
Units as a spreadsheetexportUnitsXLSX or text/csvunits-{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
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

cURL
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.

PDF

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: png
png or svg. SVG scales without loss and suits printing.
sizeintegerquerydefault: 512
Width and height of the PNG in pixels, 128 to 2048. Ignored for SVG.

The 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: xlsx
xlsx or csv. CSV is UTF-8 with a byte order mark, comma-separated, with CRLF line ends.
battery_model_iduuidquery
Only units of this model.
model_version_iduuidquery
Only units of this model version.
passport_statusstringquery
Only units whose passport has this status, e.g. published.
serial_prefixstringquery
Only serial numbers that start with this text.
import_iduuidquery
Only units created by this import.
cURL
curl -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 codeTypical cause
400 validation_errorsize outside 128 to 2048; an unknown format or lang; more than 10 000 units in exportUnits
401 unauthenticatedKey missing, unknown or revoked
403 forbiddenThe key lacks the scope read
404 not_foundUnknown unit, or no version with this number
409 conflictgetPassportQr before the identifiers are locked
429 rate_limitedToo many requests; wait for Retry-After

On this page