Units from production
Stage the serial numbers of finished batteries as an import with a key of scope import; members review, commit, approve and publish in the portal.
When production confirms finished batteries, their serial numbers should become units in BatteryPass without
anyone retyping them. In this workflow the middleware stages a spreadsheet import with a key of scope import.
Committing the import and approving its values stay with members of your organisation in the portal: a key
can prepare the data, but declared data enters BatteryPass only through a person's decision (and the four-eyes
rule, where your organisation has it switched on).
sequenceDiagram
participant ERP
participant MW as Middleware
participant BP as BatteryPass API
participant M as Member in the portal
ERP->>ERP: post goods receipt, write outbox row
MW->>ERP: read outbox rows (serial, GTIN, date)
MW->>BP: createImport with file and preset (scope import)
BP-->>MW: 201 import, status uploaded
MW->>BP: validateImport (scope import)
BP-->>MW: 202 validation job
loop every few seconds
MW->>BP: getImport (scope import)
end
BP-->>MW: status validated, summary
MW->>ERP: mark rows done, report the summary
M->>BP: review the report, commit the import
M->>BP: approve the imported values
M->>BP: publish the passports
Trigger: The production confirmation or the goods receipt from production posts the serial numbers of finished batteries.
Key scopes: import
| # | Who | What | Operation |
|---|---|---|---|
| 1 | ERP | Posts the confirmation or goods receipt with the serial numbers and records the change in an outbox table. | |
| 2 | Middleware | Collects the serial numbers of a batch with model identifier or GTIN and manufacturing date and writes a CSV or XLSX file with the preset's column names. | |
| 3 | Middleware | Uploads the file with the preset and keeps the import id. | createImport import |
| 4 | Middleware | Starts the validation of every row. | validateImport import |
| 5 | Middleware | Polls the import until it is validated or failed and reports the summary (valid, warnings, errors, duplicates) back to production. | getImport import |
| 6 | Member in the portal | Reviews the validation report in the portal and commits the import; the units are created as drafts. | |
| 7 | Member in the portal | Approves the imported values in one step (ADR-015); with the organisation's four-eyes rule switched on, values the approver recorded stay open. | |
| 8 | Member in the portal | Publishes the passports once no required value is missing. |
Security
- A key of its own with the scope import only; it can stage imports but neither commit nor approve them.
- A repeated upload creates a second import; its rows become duplicates once the first is committed, so no unit is created twice.
- The outbox in the ERP makes sure no posting is lost while the middleware or BatteryPass is unreachable.
Data minimisation
- The file holds the serial number, model identifier or GTIN, manufacturing date and at most facility and model version.
- Customers, orders, prices and employee names stay in the ERP.
Before you start
- An owner creates a key with the scope
importonly, for exampleMES line 2 imports, under Settings → API keys. It cannot read your passports, report condition data or commit anything. - The battery models exist in BatteryPass with their GTIN or model identifier, and a model version covers the manufacturing dates you send. The import finds each row's model by the GTIN, the model identifier or the product name.
- Pick an import preset that matches what your middleware writes. The preset
generic-csvtakes the columnsGTIN,Serial number,Manufacturing date,Facility(name or GLN),Model versionandBattery status, in German or English. ERP-specific presets (SAP, Business Central, Odoo, NetSuite) are listed on each ERP page.
1. Write the file
The middleware collects the serial numbers of one batch from its outbox and writes a CSV (UTF-8, separated by
;, ,, tab or |) or XLSX file with the preset's headers. At most 10 MB, 10 000 rows and 200 columns per
file. Columns the preset does not know are left unmapped, so extra columns do no harm.
GTIN;Serial number;Manufacturing date;Facility
04012345000023;SV2027-000901;2026-10-23;Werk Landshut
04012345000023;SV2027-000902;2026-10-23;Werk Landshut2. Upload with the preset
createImport takes the file as multipart/form-data in the
field file, and the preset in preset. The answer is the import with the mapping taken from the preset:
curl -sS -X POST "$BATTERYPASS_URL/api/v1/imports" \
-H "Authorization: Bearer $BATTERYPASS_IMPORT_KEY" \
-F "file=@batch-2026-10-24.csv;type=text/csv" \
-F "preset=generic-csv"{
"id": "0192f3c1-7a2b-7c3d-8e4f-5a6b7c8d9e0f",
"status": "uploaded",
"filename": "batch-2026-10-24.csv",
"format": "csv",
"row_count": 2,
"headers": ["GTIN", "Serial number", "Manufacturing date", "Facility"],
"mapping": [
{ "column": 0, "header": "GTIN", "target": "identification.gtin" },
{ "column": 1, "header": "Serial number", "target": "identification.serial_number" },
{ "column": 2, "header": "Manufacturing date", "target": "identification.manufacturing_date" },
{ "column": 3, "header": "Facility", "target": "identification.manufacturing_place" }
],
"options": {},
"preset": "generic-csv",
"created_by_api_key": { "id": "0192f0aa-1b2c-7d3e-8f40-5a6b7c8d9e01", "name": "MES line 2 imports" },
"created_at": "2026-10-24T06:05:11Z",
"updated_at": "2026-10-24T06:05:11Z",
"row_version": 1
}The import names the key in created_by_api_key; members see it in the portal's import list as staged by that
key.
3. Optional: set options
When the file has no model column, or no facility column, set them for every row with
updateImportMapping. It replaces the mapping, so send the
preset again (or a full mapping, never both), and If-Match with the import's row_version:
curl -sS -X PUT "$BATTERYPASS_URL/api/v1/imports/0192f3c1-7a2b-7c3d-8e4f-5a6b7c8d9e0f/mapping" \
-H "Authorization: Bearer $BATTERYPASS_IMPORT_KEY" \
-H 'If-Match: "1"' \
-H "Content-Type: application/json" \
-d '{"preset": "generic-csv", "options": {"facility_id": "0192f09e-4a5b-7c6d-9e8f-0a1b2c3d4e5f"}}'A changed import answers 409 conflict: read it again with getImport
and retry with the new row_version.
4. Validate and wait
validateImport starts a job that checks every row with the
portal's validators and finds serial numbers that repeat in the file or already exist. Poll
getImport until status is validated (or failed, when the file
could not be read):
curl -sS -X POST "$BATTERYPASS_URL/api/v1/imports/0192f3c1-7a2b-7c3d-8e4f-5a6b7c8d9e0f/validate" \
-H "Authorization: Bearer $BATTERYPASS_IMPORT_KEY"
curl -sS "$BATTERYPASS_URL/api/v1/imports/0192f3c1-7a2b-7c3d-8e4f-5a6b7c8d9e0f" \
-H "Authorization: Bearer $BATTERYPASS_IMPORT_KEY" | jq '{status, summary}'{
"status": "validated",
"summary": { "rows": 2, "valid": 2, "warnings": 0, "errors": 0, "duplicates": 0, "created": 0, "approved": 0, "not_approved": 0 }
}Mark the outbox rows done once the import is validated, and send the summary to whoever runs production. Rows with errors or duplicates are not imported; the members see each one with row, column and reason in the portal.
5. Members commit, approve and publish
From here on the work happens in the portal, never with a key:
- An editor opens the import, reads the validation report and commits it. The units are created as drafts,
their values carry the source
import. - A reviewer approves the imported values in one step. Values with a failing check, without required evidence or with an import warning stay open for individual review; with the four-eyes rule switched on, values the reviewer recorded themselves stay open too.
- A reviewer publishes the passports once no required value is missing.
The change feed reports import.committed,
battery.created, import.approved and later passport.published, so the ERP learns about each step without
asking the people.
Failure handling
| Answer | Meaning | What to do |
|---|---|---|
400 validation_error | Field file (empty, not CSV or XLSX), preset (unknown preset), mapping (no serial number column or no way to find the model) | Fix the file or the preset; do not retry unchanged. |
401 unauthenticated | The key is missing, unknown or revoked | Check the secret; alert. |
403 forbidden | The key lacks the scope import | Use the import key. |
404 not_found | The import does not exist in your organisation | Start a new import. |
409 conflict | The import changed (If-Match) or is being validated or committed | Read the import and decide. |
413 payload_too_large | The file is larger than the upload limit | Split the batch. |
429 rate_limited | Too many requests | Wait Retry-After seconds. |
5xx, timeout | Unknown outcome | Retry with backoff. A repeated upload creates a second import whose rows become duplicates once the first is committed; members commit only one. |
Status failed means the file could not be read during validation: upload a corrected file as a new import.
Security
Keys and scopes, secrets and rotation, transport, safe retries, the outbox, thin events, stale writes, data minimisation, the audit trail and the OWASP API Top 10.
Passport links back into the ERP
Follow the change feed with a read key and store each unit's GS1 Digital Link, passport status and QR code on its serial number record in the ERP.