Push condition data from a battery management system
Report measured values such as state of charge or full cycles with updateCondition, and read what was accepted, skipped and re-published.
A battery management system (BMS), a service tool or a monitoring platform reports what it measures with
updateCondition. BatteryPass accepts the values as
measurements, without a review step, records them with their measurement time and source api, adds a
condition_update event that names the key (api_key) to the unit's lifecycle and re-publishes the passport according to your organisation's
policy. The concepts are in Condition data and lifecycle.
Before you start
- A key with the scope
condition. An owner creates it in the portal under Settings → API keys. Reading (for examplegetConditionorlistBatteries) needs the scoperead; give the key both scopes if the system does both. See Handle keys securely. - The unit's id. Store it next to the serial number in your system, for example with
Sync units, or look it up with
listBatteries?serial_prefix=. - The fields. Only the dynamic fields of the registry (section
condition) are accepted, and only those that apply to the unit's battery category.getConditionlists them with their current values and tells you the re-publication policy:
curl -sS "$BATTERYPASS_URL/api/v1/batteries/0192a3f0-5b7c-7d21-8e4f-6a2b9c1d3e5f/condition" \
-H "Authorization: Bearer $BATTERYPASS_API_KEY"{
"battery_id": "0192a3f0-5b7c-7d21-8e4f-6a2b9c1d3e5f",
"field_keys": [
"condition.state_of_charge",
"condition.remaining_capacity",
"condition.capacity_fade",
"condition.number_of_full_cycles"
],
"values": [
{
"field_key": "condition.number_of_full_cycles",
"value": 412,
"revision": 6,
"source": "api",
"review_status": "approved",
"measured_at": "2026-10-23T06:00:00Z",
"recorded_at": "2026-10-23T06:00:04Z",
"registry_version": "0.5.0",
"checks": []
}
],
"republish": "immediate"
}Send an update
PATCH /api/v1/batteries/{batteryId}/condition with a JSON body:
valuesobject[]bodyrequired1 to 50 values, each field at most once.
field_keystringrequiredcondition.state_of_charge.valueanyrequired% or Ah); null clears the field.measured_atdate-timemeasured_at.measured_atdate-timebodynotestringbodycurl -sS -X PATCH "$BATTERYPASS_URL/api/v1/batteries/0192a3f0-5b7c-7d21-8e4f-6a2b9c1d3e5f/condition" \
-H "Authorization: Bearer $BATTERYPASS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"measured_at": "2026-10-24T06:00:00Z",
"note": "BMS daily report",
"values": [
{ "field_key": "condition.state_of_charge", "value": 81.5 },
{ "field_key": "condition.number_of_full_cycles", "value": 412 },
{ "field_key": "condition.remaining_capacity", "value": 187.2, "measured_at": "2026-10-24T05:40:00Z" }
]
}'{
"accepted": [
{
"field_key": "condition.state_of_charge",
"value": 81.5,
"unit": "%",
"revision": 9,
"source": "api",
"review_status": "approved",
"measured_at": "2026-10-24T06:00:00Z",
"recorded_at": "2026-10-24T06:00:03Z",
"reviewed_at": "2026-10-24T06:00:03Z",
"registry_version": "0.5.0",
"checks": []
},
{
"field_key": "condition.remaining_capacity",
"value": 187.2,
"unit": "Ah",
"revision": 5,
"source": "api",
"review_status": "approved",
"measured_at": "2026-10-24T05:40:00Z",
"recorded_at": "2026-10-24T06:00:03Z",
"reviewed_at": "2026-10-24T06:00:03Z",
"registry_version": "0.5.0",
"checks": []
}
],
"skipped": [
{ "field_key": "condition.number_of_full_cycles", "reason": "unchanged" }
],
"event": {
"id": "0192f8b1-2a3c-7d4e-9f50-61a2b3c4d5e6",
"battery_id": "0192a3f0-5b7c-7d21-8e4f-6a2b9c1d3e5f",
"type": "condition_update",
"occurred_on": "2026-10-24",
"description": "BMS daily report",
"source": "api",
"api_key": { "id": "0192e4c7-8b9a-7c6d-8e5f-4a3b2c1d0e9f", "name": "BMS Landshut" },
"field_keys": ["condition.state_of_charge", "condition.remaining_capacity"],
"evidence": [],
"created_at": "2026-10-24T06:00:03Z"
},
"publication": {
"outcome": "published",
"version": {
"version_no": 4,
"published_at": "2026-10-24T06:00:03Z",
"reason": "condition update",
"registry_version": "0.5.0",
"canonical_hash": "sha256:5d0c9e8f7a6b5c4d3e2f1a0b9c8d7e6f5a4b3c2d1e0f9a8b7c6d5e4f3a2b1c0d",
"public_hash": "sha256:7e6f5a4b3c2d1e0f9a8b7c6d5e4f3a2b1c0d5d0c9e8f7a6b5c4d3e2f1a0b9c8d",
"key_id": "2026-10-k1",
"batterypass_v2_valid": true,
"batterypass_v2_issue_count": 0
}
}
}Read the result
accepted holds the stored values with source: api, review_status: approved and their measured_at.
skipped lists values that were valid but not stored:
reason | Meaning |
|---|---|
older_measurement | The stored value was measured later than the one you sent. |
unchanged | The stored value has the same value and the same measurement time, or you cleared a field that has no value. |
event is the condition_update event in the unit's lifecycle; it is missing when nothing was accepted. Its
occurred_on is the UTC day of the latest accepted measurement, and note becomes its description.
publication.outcome says what happened to the passport:
outcome | Meaning |
|---|---|
published | A new passport version with the reason condition update is online now (version). |
unchanged | Published immediately, but the public content did not change, so no version was added. |
scheduled | The organisation re-publishes daily: the job at 02:00 UTC publishes the new values. |
blocked | The publish gate refused (blockers). The values are stored; the passport stays online in status update_needed until a member publishes it again. |
not_published | The unit has no public passport; the values appear with its first publication. |
The policy (immediate or daily) is an organisation setting in the portal; getCondition shows it as
republish. Most condition fields belong to the access tier legitimate_interest: they are part of the
signed snapshot and the restricted views, while the public JSON only lists their keys in
restricted_fields_present (see Access tiers).
Rules of an update
- Merge. Only the fields you name change; every other field keeps its value.
nullclears a field. - Measurement time. Every value needs a
measured_at, its own or the request's. It may lie at most five minutes ahead of the server clock and not before the unit's manufacturing date. Send it in UTC (Z). - Newest wins. A value measured earlier than the stored one is skipped, so late or repeated deliveries do no
harm. Sending the same update twice is safe: the second time everything comes back as
unchanged. - All or nothing. If one value is invalid, or the merged condition data fails a cross-field check of the registry, the whole update is refused and nothing is stored.
- Values, not documents.
updateConditiontakes noevidence_ids; to attach a diagnostic report, record a lifecycle event.
Errors
{
"type": "about:blank",
"title": "Bad Request",
"status": 400,
"code": "validation_error",
"detail": "The request failed validation.",
"request_id": "4f9c2a7d1e0b5c8a3f6e2d19",
"errors": [
{ "field": "values[0].field_key", "code": "unknown_field", "message": "performance.nominal_voltage is not condition data of this unit" },
{ "field": "values[1].measured_at", "code": "out_of_range", "message": "measured_at lies in the future" }
]
}Status and code | Typical cause | What to do |
|---|---|---|
400 validation_error | values[i].field_key with unknown_field (not a dynamic field of this unit); values[i].measured_at with required or out_of_range; values[i].value outside the field's range; a field twice; a failing cross-field check; note too long | Fix the data; do not retry unchanged |
401 unauthenticated | Key missing, unknown or revoked | Check the key in your secret store |
403 forbidden | The key lacks the scope condition | Have an owner create a key with that scope |
404 not_found | The unit does not exist in the key's organisation | Check the id |
409 conflict | The unit's battery model is archived: its data can be read but not changed | Stop reporting for units of that model |
429 rate_limited | More than 600 requests per minute for all keys of the organisation | Wait for Retry-After, then send again |
Handle errors and retries shows a retry helper; the recipe Nightly condition push runs one update per unit with backoff.
Verify a signature
Check with the public JSON and the published keys alone that the public data of a passport is exactly what its BatteryPass instance signed.
Record lifecycle events
Report inspections, repairs and other events in the life of a unit with createLifecycleEvent, and read the lifecycle with listLifecycleEvents.