openapi: 3.0.3
info:
  title: Traceable BMS Ingestion API
  version: "1.0.0"
  description: |
    BatteryPass v1.3 dynamic-data ingestion webhook.

    Lets a Battery Management System (BMS) push runtime telemetry (state of
    health, cycle count, lifetime-counter values) into a product's Digital
    Product Passport so that downstream consumers — the DPP public page, JSON-LD,
    and the BatteryPass v1.3 regulation-conformance score — always see
    up-to-date values.

    Disabled by default. Ops must set env var `NEXT_PUBLIC_BMS_INGESTION_ENABLED=true`
    on the target environment before the endpoint will accept writes.

servers:
  - url: https://app.traceable.digital
    description: Production
  - url: https://staging.traceable.digital
    description: Staging

paths:
  /api/v1/products/{id}/bms-update:
    post:
      operationId: bmsUpdate
      summary: Push BMS telemetry for a single battery DPP
      description: |
        Authentication requires both:
          * `X-Traceable-BMS-Secret` — plaintext per-company secret. Server
            looks up `BatteryBmsWebhookSecret` via `sha256(plaintext)`.
          * `X-Traceable-Signature: t=<unix_sec>, v1=<hex_hmac>` — HMAC-SHA256
            of `${timestamp}.${raw_json_body}`. Replay window: 5 min.

        The signature header is mandatory — it binds the request to a specific
        body and prevents replay even if the plaintext secret leaks briefly.

        Only keys in the allow-list are applied to `products.form_data`; all
        other keys are silently rejected (logged to audit context). Lifetime
        counters upsert into the dedicated `battery_lifetime_counter` table.

        Every successful request creates an AuditLog entry with
        `actorRole=System`, `eventChannel=API`, `source=bms-webhook`.
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
          description: Product UUID
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/BmsUpdateRequest"
      responses:
        "200":
          description: Write succeeded (may be partial — inspect rejectedKeys)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BmsUpdateResponse"
        "400":
          description: Malformed JSON body
        "401":
          description: Missing / invalid secret or HMAC signature
        "403":
          description: Secret does not grant access to this product
        "404":
          description: Product not found
        "503":
          description: BMS ingestion feature flag disabled on this environment

components:
  schemas:
    BmsUpdateRequest:
      type: object
      properties:
        formDataPatch:
          type: object
          additionalProperties: true
          description: |
            Keys merged into `products.form_data`. Only whitelisted keys are
            applied; others go into `rejectedKeys` in the response. Currently
            whitelisted:
              current_soh_pct, soh_last_updated, current_cycle_count,
              cycle_count_last_updated, remaining_energy_kwh,
              remaining_capacity_ah, deep_discharge_events, overcharge_events,
              number_of_charging_events, remaining_capacity_kwh,
              remaining_power_capability_kw, remaining_round_trip_efficiency_pct,
              self_discharge_rate_evolution_pct_per_month,
              total_energy_throughput_kwh, total_energy_throughput_ah,
              time_at_temp_extreme_low_sec, time_at_temp_low_sec,
              time_at_temp_high_sec, time_at_temp_extreme_high_sec,
              time_charging_at_temp_extreme_high_sec,
              time_charging_at_temp_extreme_low_sec,
              lifetime_counter_last_updated.
        lifetimeCounters:
          type: object
          description: BatteryPass v1.3 Annex XIII §60-98 counters. Upserted into the `battery_lifetime_counter` table.
          properties:
            timeAtTempExtremeLowSec:
              type: integer
              nullable: true
            timeAtTempLowSec:
              type: integer
              nullable: true
            timeAtTempHighSec:
              type: integer
              nullable: true
            timeAtTempExtremeHighSec:
              type: integer
              nullable: true
            totalEnergyThroughputKwh:
              type: number
              format: double
              nullable: true
            totalEnergyThroughputAh:
              type: number
              format: double
              nullable: true
            deepDischargeEvents:
              type: integer
              nullable: true

    BmsUpdateResponse:
      type: object
      required: [ok, productId, appliedKeys, rejectedKeys, counterUpdated]
      properties:
        ok:
          type: boolean
        productId:
          type: string
          format: uuid
        appliedKeys:
          type: array
          items:
            type: string
        rejectedKeys:
          type: array
          items:
            type: string
        counterUpdated:
          type: boolean

  securitySchemes:
    BmsSecret:
      type: apiKey
      in: header
      name: X-Traceable-BMS-Secret
    BmsSignature:
      type: apiKey
      in: header
      name: X-Traceable-Signature

security:
  - BmsSecret: []
    BmsSignature: []
