/api/v1/passports/{id}/measurementsCapture measurements
Battery passports only (Regulation (EU) 2023/1542 Art. 77, Annex XIII point 4 use data). The passport must be published; a draft or suspended passport returns 422 passport_not_active. A non-battery passport returns 422 not_a_battery. Send 1–500 measurements per call, each as { fieldKey, value, measuredAt, externalId?, unit? } — measuredAt is an ISO 8601 timestamp, must not be in the future; value must be at most 16 KB when serialised (a larger value returns 400 invalid_value). Accepted fieldKeys are the Annex XIII point 4 use-data fields: stateOfHealth, stateOfCertifiedEnergy, remainingCapacity, remainingPowerCapability, remainingRoundTripEfficiency, evolutionOfSelfDischargeRate, currentInternalResistancePack, capacityFade, powerFade, internalResistanceIncrease, dynamicRatedCapacity, dynamicPowerCapability, dynamicInternalResistance, dynamicEnergyRoundTripEfficiency, dynamicExpectedLifetimeCycles, numberOfFullEquivalentChargingCycles, numberOfChargingEvents, currentStateOfCharge, negativeEvents, temperatureConditionsHistorical.
Every measurement is stored. The newest per field (by measuredAt) also becomes the passport's current value for that field and sets dynamicDataAsOf on the passport. A later-arriving older measurement is kept in history but does not overwrite a newer current value. Providing an externalId makes a measurement idempotent: a repeat submission is counted in duplicates, not stored or metered again. The Idempotency-Key header is also supported — same key + same body replays the cached response for 24 h; same key + different body returns 422. OAuth scope: passports:write.
batteryStatus is not a measurement field — lifecycle status changes go through the dashboard. A repurposed, remanufactured, or reused battery gets a new passport linked to the original via lineage on passport create. A field the Regulation keeps off the battery's category returns 422 field_not_applicable (e.g. stateOfCertifiedEnergy is off-limits for LMT batteries; the five Annex VII Part A remaining-capacity metrics are off-limits for EV batteries). An alternate addressing form exists at POST /api/v1/passports/by-serial/{serial}/measurements — same body, same response. If a serial is not unique within your account, add ?gtin=<gtin> to disambiguate.
Measurements count against the plan's monthly measurement allowance (maxMeasurementsPerMonth), not the daily v1 write budget. Paid plans keep accepting and counting measurements past the allowance at no charge; the Free plan stops at its allowance. Viewing a passport is never metered.
Path parameters
- idrequired
string (24 hex chars)
Passport ID.
Headers
- Authorizationrequired
string
Bearer <token>— either atp_API key (Developer → API Keys; simplest, for server-to-server) or an OAuth 2.0 access token (Developer → OAuth Apps; for user-authorized apps, scoped + revocable). The Authentication page has the full OAuth flow and scope list.e.g. Bearer tp_REDACTED_xxxxxxxxxxxx
- Idempotency-Key
string
Optional idempotency key (UUID v4 or any opaque string ≤ 64 chars). Same key + same body replays the cached response for 24 h; same key + different body returns 422.
Request
curl -sS -X POST \
https://app.tracepass.eu/api/v1/passports/6650b2c3d4e5f6a7b8c9d0e1/measurements \
-H "Authorization: Bearer tp_REDACTED_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"measurements": [
{
"fieldKey": "stateOfHealth",
"value": 96.4,
"measuredAt": "2027-03-01T06:00:00Z",
"externalId": "bms-7781-2027-03-01"
},
{
"fieldKey": "numberOfFullEquivalentChargingCycles",
"value": 112,
"measuredAt": "2027-03-01T06:00:00Z"
}
]
}'Response
{
"accepted": 2,
"storedCount": 2,
"materializedCount": 2,
"duplicates": 0,
"measurementIds": [
"6750a1b2c3d4e5f6a7b8c9d0",
"6750a1b2c3d4e5f6a7b8c9d1"
]
}