Skip to content

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 example getCondition or listBatteries) needs the scope read; 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. getCondition lists them with their current values and tells you the re-publication policy:
cURL
curl -sS "$BATTERYPASS_URL/api/v1/batteries/0192a3f0-5b7c-7d21-8e4f-6a2b9c1d3e5f/condition" \
  -H "Authorization: Bearer $BATTERYPASS_API_KEY"
200 (shortened)
{
  "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[]bodyrequired

1 to 50 values, each field at most once.

measured_atdate-timebody
The measurement time of every value without its own.
notestringbody
At most 500 characters; becomes the description of the lifecycle event.
curl -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" }
    ]
  }'
200 (shortened)
{
  "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:

reasonMeaning
older_measurementThe stored value was measured later than the one you sent.
unchangedThe 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:

outcomeMeaning
publishedA new passport version with the reason condition update is online now (version).
unchangedPublished immediately, but the public content did not change, so no version was added.
scheduledThe organisation re-publishes daily: the job at 02:00 UTC publishes the new values.
blockedThe publish gate refused (blockers). The values are stored; the passport stays online in status update_needed until a member publishes it again.
not_publishedThe 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. null clears 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. updateCondition takes no evidence_ids; to attach a diagnostic report, record a lifecycle event.

Errors

400
{
  "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 codeTypical causeWhat to do
400 validation_errorvalues[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 longFix the data; do not retry unchanged
401 unauthenticatedKey missing, unknown or revokedCheck the key in your secret store
403 forbiddenThe key lacks the scope conditionHave an owner create a key with that scope
404 not_foundThe unit does not exist in the key's organisationCheck the id
409 conflictThe unit's battery model is archived: its data can be read but not changedStop reporting for units of that model
429 rate_limitedMore than 600 requests per minute for all keys of the organisationWait 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.

On this page