# Returns and repairs as lifecycle events

> Completed service, repair and return orders in the ERP become lifecycle events of the unit, without customer data and without duplicates.

When a battery comes back for inspection, repair or a component replacement, the ERP or its service module closes
an order. In this workflow the middleware turns the completed order into a lifecycle event of the unit with
[`createLifecycleEvent`](https://passport.kabin.sh/docs/en/api-reference/lifecycle/create-lifecycle-event). Events are records: they appear
in the unit's history and change no passport data. How events work is described in
[Record lifecycle events](https://passport.kabin.sh/docs/en/guides/record-lifecycle-events).

```mermaid
sequenceDiagram
  participant ERP
  participant MW as Middleware
  participant BP as BatteryPass API
  participant M as Member in the portal
  ERP->>ERP: complete the order, write outbox row
  MW->>ERP: read outbox row (serial, kind of work, date)
  MW->>MW: already reported this order? then stop
  MW->>BP: listBatteries with serial_prefix (scope read)
  MW->>BP: createLifecycleEvent (scope condition)
  BP-->>MW: 201 event
  MW->>MW: remember order and event id
  M->>BP: check the history, create a second life if needed
```

**Trigger:** A service, repair or return order is completed in the ERP.

**Key scopes:** read, condition

| # | Who                  | What                                                                                                            | Operation                                                                                                                          |
| - | -------------------- | --------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| 1 | ERP                  | Completes the order with the serial number and the kind of work and records the change in the outbox.           |                                                                                                                                    |
| 2 | Middleware           | Finds the unit by its serial number.                                                                            | [`listBatteries`](https://passport.kabin.sh/docs/en/api-reference/catalog/list-batteries.md) (Scope: read)                       |
| 3 | Middleware           | Records the event (for example repair, inspection or component_replaced) with its date and a short description. | [`createLifecycleEvent`](https://passport.kabin.sh/docs/en/api-reference/lifecycle/create-lifecycle-event.md) (Scope: condition) |
| 4 | Member in the portal | Checks the history in the portal; a member creates a second life (repurposing, remanufacturing) as a new unit.  |                                                                                                                                    |

**Security**

- Events are records and change no passport data; the key needs only condition and read.
- The middleware remembers the order number it recorded an event for and does not record it a second time.

**Data minimisation**

- No customer names, addresses, order numbers or fault descriptions that identify people in the description; the serial number links event and order.

## Map the order to an event type

| ERP order                            | `type`               |
| ------------------------------------ | -------------------- |
| Inspection or diagnosis without work | `inspection`         |
| Repair                               | `repair`             |
| A module, cell or part replaced      | `component_replaced` |
| Battery taken back for disposal      | `waste`              |
| Battery recycled                     | `recycled`           |
| Anything else worth recording        | `custom`             |

Condition values measured during service are not part of the event: report them with
[`updateCondition`](https://passport.kabin.sh/docs/en/integrations/workflows/condition-from-bms), which records its own `condition_update`
event. A repurposed or remanufactured battery gets a new unit and passport, which a member creates in the portal
(second life).

## Record the event

`occurred_on` is the day the work was done, between the manufacturing date and today. `description` takes at most
2 000 characters; `evidence_ids` can name up to 20 documents of your evidence library, such as a repair protocol.

```bash title="cURL"
curl -sS -X POST "$BATTERYPASS_URL/api/v1/batteries/0192a3f0-5b7c-7d21-8e4f-6a2b9c1d3e5f/lifecycle-events" \
  -H "Authorization: Bearer $BATTERYPASS_SERVICE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "component_replaced", "occurred_on": "2026-10-21", "description": "Module 3 replaced (service code R-12)"}'
```

```ts title="TypeScript"
const BASE = `${process.env.BATTERYPASS_URL}/api/v1`;
const headers = { Authorization: `Bearer ${process.env.BATTERYPASS_SERVICE_KEY}`, 'Content-Type': 'application/json' };

// reported: your own table of orders already sent (order number → event id).
export async function recordOrder(order: { number: string; batteryId: string; type: string; doneOn: string; code: string },
  reported: Map<string, string>) {
  if (reported.has(order.number)) return reported.get(order.number); // never send an order twice
  const res = await fetch(`${BASE}/batteries/${order.batteryId}/lifecycle-events`, {
    method: 'POST',
    headers,
    body: JSON.stringify({ type: order.type, occurred_on: order.doneOn, description: `Service code ${order.code}` }),
  });
  const body = await res.json();
  if (res.status !== 201) throw new Error(`createLifecycleEvent: ${res.status} ${body.code} (request_id ${body.request_id})`);
  reported.set(order.number, body.id);
  return body.id;
}
```

```python title="Python"
import os

import requests

BASE = os.environ["BATTERYPASS_URL"] + "/api/v1"
session = requests.Session()
session.headers["Authorization"] = "Bearer " + os.environ["BATTERYPASS_SERVICE_KEY"]


def record_order(order, reported):
    """reported: your own table of orders already sent (order number -> event id)."""
    if order["number"] in reported:
        return reported[order["number"]]  # never send an order twice
    res = session.post(
        f"{BASE}/batteries/{order['battery_id']}/lifecycle-events",
        json={"type": order["type"], "occurred_on": order["done_on"], "description": f"Service code {order['code']}"},
        timeout=30,
    )
    body = res.json()
    if res.status_code != 201:
        raise SystemExit(f"createLifecycleEvent: {res.status_code} {body['code']} (request_id {body.get('request_id')})")
    reported[order["number"]] = body["id"]
    return body["id"]
```

The event has the source `api` and names the key in `api_key`; the audit log names the key as the actor.

## No duplicates

`createLifecycleEvent` is not idempotent: every successful call records a new event. Keep a table of the orders you
reported (order number, BatteryPass event id). After a timeout, look at
[`listLifecycleEvents`](https://passport.kabin.sh/docs/en/api-reference/lifecycle/list-lifecycle-events) of the unit (it needs `read`)
before you send again: an event of the same type and day with your service code means the first call went through.

## What never flows

No customer names, addresses or contact data, no order or invoice numbers, no technician names and no fault
descriptions that identify people. The serial number links the event to the order in your ERP. Build the
`description` from fixed codes, as above, not from the order's free text.

## Failure handling

| Answer                 | Meaning                                                                                                                                                         | What to do                                     |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------- |
| `400 validation_error` | Unknown `type` (also `condition_update`, which only `updateCondition` records), `occurred_on` before manufacture or in the future, a document that is not yours | Fix the mapping; do not retry unchanged.       |
| `401 unauthenticated`  | Key missing, unknown or revoked                                                                                                                                 | Check the secret; alert.                       |
| `403 forbidden`        | The key lacks the scope `condition`                                                                                                                             | Use the service key.                           |
| `404 not_found`        | The unit is not in your organisation                                                                                                                            | Check the serial number.                       |
| `409 conflict`         | The unit's model is archived                                                                                                                                    | Record the event in the portal or not at all.  |
| `429 rate_limited`     | Too many requests                                                                                                                                               | Wait `Retry-After` seconds.                    |
| `5xx`, timeout         | Unknown outcome                                                                                                                                                 | Check `listLifecycleEvents` first, then retry. |
