{
  "asyncapi": "3.1.0",
  "info": {
    "title": "Tensor Cloud battery optimization API",
    "version": "2.9.0",
    "description": "AsyncAPI specification for EMS communication with the Tensor Cloud battery optimization service over MQTT. For windowed energy readings and power or grid frequency readings with aggregation 'average', 'measurement_ts' is the start of the covered period, not its end or midpoint. For point-in-time telemetry, it is the measurement instant. Curtailment schedules use the schedule creation time issued by the TSO, or the time the EMS received the schedule if the TSO does not provide one.",
    "termsOfService": "https://docs.tensorenergy.jp/en/legal/terms-of-use/current",
    "externalDocs": {
      "description": "Tensor Cloud battery optimization documentation",
      "url": "https://docs.tensorenergy.jp/en/guides/ems-integration/integrator-guide"
    },
    "contact": {
      "name": "Tensor Energy",
      "url": "https://www.tensorenergy.jp"
    }
  },
  "servers": {
    "testing": {
      "host": "mqtt.staging.tensorenergy.jp",
      "protocol": "mqtt",
      "protocolVersion": "3.1.1",
      "description": "Tensor Cloud MQTT broker for testing (AWS IoT Core)",
      "security": [
        {
          "$ref": "#/components/securitySchemes/awsIotCore"
        }
      ]
    },
    "production": {
      "host": "mqtt.tensorenergy.jp",
      "protocol": "mqtt",
      "protocolVersion": "3.1.1",
      "description": "Tensor Cloud production MQTT broker (AWS IoT Core)",
      "security": [
        {
          "$ref": "#/components/securitySchemes/awsIotCore"
        }
      ]
    }
  },
  "defaultContentType": "application/json",
  "channels": {
    "lifetimeTelemetry": {
      "address": "dt/{siteId}/{gatewayId}/{metric}/lifetime",
      "title": "Lifetime Energy Telemetry",
      "summary": "Publish lifetime cumulative energy (kWh) to this topic. Tensor Cloud requires either lifetime or windowed readings; sending both is preferred.",
      "parameters": {
        "siteId": {
          "$ref": "#/components/parameters/siteId"
        },
        "gatewayId": {
          "$ref": "#/components/parameters/gatewayId"
        },
        "metric": {
          "$ref": "#/components/parameters/lifetimeMetric"
        }
      },
      "messages": {
        "energyLifetime": {
          "$ref": "#/components/messages/EnergyLifetime"
        }
      }
    },
    "energyTelemetry": {
      "address": "dt/{siteId}/{gatewayId}/{metric}/energy/{window}",
      "title": "Windowed Energy Telemetry",
      "summary": "Publish aggregated energy (kWh per window) to this topic. Tensor Cloud requires either windowed or lifetime readings; sending both is preferred.",
      "parameters": {
        "siteId": {
          "$ref": "#/components/parameters/siteId"
        },
        "gatewayId": {
          "$ref": "#/components/parameters/gatewayId"
        },
        "metric": {
          "$ref": "#/components/parameters/energyMetric"
        },
        "window": {
          "$ref": "#/components/parameters/window"
        }
      },
      "messages": {
        "energyWindowed": {
          "$ref": "#/components/messages/EnergyWindowed"
        }
      }
    },
    "powerTelemetry": {
      "address": "dt/{siteId}/{gatewayId}/{metric}/power",
      "title": "Instantaneous Power Telemetry",
      "description": "Publish instantaneous power (kW) telemetry to topics matching this pattern. These readings are optional for battery optimization, but readings sent every 10 minutes or less can improve accuracy. For an offline-monitored FCR resource, this topic also carries the 1-second supplied-power readings used for FCR Assessment II. During every FCR-awarded slot, publish meter_export_ac and meter_import_ac at 1 Hz for grid-connection-point registration, or battery_discharge_ac and battery_charge_ac for equipment-point registration. See 'FCR Assessment II reporting' in the integration guide.",
      "parameters": {
        "siteId": {
          "$ref": "#/components/parameters/siteId"
        },
        "gatewayId": {
          "$ref": "#/components/parameters/gatewayId"
        },
        "metric": {
          "$ref": "#/components/parameters/powerMetric"
        }
      },
      "messages": {
        "powerInstant": {
          "$ref": "#/components/messages/PowerInstant"
        }
      }
    },
    "stateTelemetry": {
      "address": "dt/{siteId}/{gatewayId}/{metric}/state",
      "title": "Device State Telemetry",
      "summary": "Publish the device's current operational state to this topic. Battery optimization requires battery SoE and remaining energy.",
      "parameters": {
        "siteId": {
          "$ref": "#/components/parameters/siteId"
        },
        "gatewayId": {
          "$ref": "#/components/parameters/gatewayId"
        },
        "metric": {
          "$ref": "#/components/parameters/batteryStateMetric"
        }
      },
      "messages": {
        "batteryState": {
          "$ref": "#/components/messages/BatteryStateValue"
        }
      }
    },
    "curtailment": {
      "address": "dt/{siteId}/{gatewayId}/curtailment",
      "title": "Curtailment Schedule",
      "description": "Publish curtailment schedules to this topic. This is required for every topology, including stand-alone battery systems.",
      "parameters": {
        "siteId": {
          "$ref": "#/components/parameters/siteId"
        },
        "gatewayId": {
          "$ref": "#/components/parameters/gatewayId"
        }
      },
      "messages": {
        "curtailment": {
          "$ref": "#/components/messages/Curtailment"
        }
      }
    },
    "irradiationTelemetry": {
      "address": "dt/{siteId}/{gatewayId}/irradiation",
      "title": "Solar Irradiation Telemetry",
      "description": "Publish on-site solar irradiation sensor readings to this topic.",
      "parameters": {
        "siteId": {
          "$ref": "#/components/parameters/siteId"
        },
        "gatewayId": {
          "$ref": "#/components/parameters/gatewayId"
        }
      },
      "messages": {
        "irradiation": {
          "$ref": "#/components/messages/Irradiation"
        }
      }
    },
    "solarDcTelemetry": {
      "address": "dt/{siteId}/{gatewayId}/solar",
      "title": "Solar DC Electrical Telemetry",
      "description": "For DC-coupled (DC-link) PV plus battery systems, publish PV-array DC voltage and current to this topic as a matched pair sampled at the same instant. This topic applies only to DC-link sites.",
      "parameters": {
        "siteId": {
          "$ref": "#/components/parameters/siteId"
        },
        "gatewayId": {
          "$ref": "#/components/parameters/gatewayId"
        }
      },
      "messages": {
        "solarDcElectrical": {
          "$ref": "#/components/messages/SolarDcElectrical"
        }
      }
    },
    "gridFrequencyTelemetry": {
      "address": "dt/{siteId}/{gatewayId}/frequency",
      "title": "Grid Frequency Telemetry",
      "description": "Publish grid frequency (Hz) telemetry to this topic. For the full duration of every FCR-awarded slot, the EMS MUST publish one reading per second, calculated as the mean over that 1-second window. Measure frequency at the grid connection point from the live AC voltage waveform and report it to at least 0.01 Hz resolution. Do not publish a nominal, scheduled, or derived value. Time-align these readings with the 1 Hz supplied-power telemetry used for FCR Assessment II. See 'FCR Assessment II reporting' in the integration guide.",
      "parameters": {
        "siteId": {
          "$ref": "#/components/parameters/siteId"
        },
        "gatewayId": {
          "$ref": "#/components/parameters/gatewayId"
        }
      },
      "messages": {
        "gridFrequency": {
          "$ref": "#/components/messages/GridFrequency"
        }
      }
    },
    "powerTelemetryBatch": {
      "address": "dt/{siteId}/{gatewayId}/{metric}/power/batch",
      "title": "Batched Instantaneous Power Telemetry",
      "description": "Optional batched form of the instantaneous power topic for high-rate 1 Hz publishing and backlog recovery after a connection loss. Each message contains multiple readings for one metric. Every reading has its own message_id and measurement_ts, while unit, aggregation, aggregation_window and pre_qualification apply to the entire batch. Tensor Cloud accepts or rejects the whole batch. Tensor Cloud enables batching per site, with backfill-only use as the default. Sites that must deliver live telemetry every second, such as low-voltage batteries in a VPP configuration, send live readings as individual messages and use batches only for backfill. See 'Batched telemetry' in the integration guide.",
      "parameters": {
        "siteId": {
          "$ref": "#/components/parameters/siteId"
        },
        "gatewayId": {
          "$ref": "#/components/parameters/gatewayId"
        },
        "metric": {
          "$ref": "#/components/parameters/powerMetric"
        }
      },
      "messages": {
        "powerInstantBatch": {
          "$ref": "#/components/messages/PowerInstantBatch"
        }
      }
    },
    "gridFrequencyTelemetryBatch": {
      "address": "dt/{siteId}/{gatewayId}/frequency/batch",
      "title": "Batched Grid Frequency Telemetry",
      "description": "Optional batched form of the grid frequency topic. It follows the batched power topic rules: each message contains multiple readings, every reading has its own message_id and measurement_ts, and unit, aggregation, aggregation_window and pre_qualification apply to the entire batch. Tensor Cloud accepts or rejects the whole batch. Tensor Cloud enables batching per site, with backfill-only use as the default. See 'Batched telemetry' in the integration guide.",
      "parameters": {
        "siteId": {
          "$ref": "#/components/parameters/siteId"
        },
        "gatewayId": {
          "$ref": "#/components/parameters/gatewayId"
        }
      },
      "messages": {
        "gridFrequencyBatch": {
          "$ref": "#/components/messages/GridFrequencyBatch"
        }
      }
    },
    "batteryPowerCommand": {
      "address": "cmd/{siteId}/battery/power",
      "title": "Battery Power Setpoint Command Schedule",
      "description": "Topic for battery real power setpoint schedules. In FCR mode, this command also sets the baseline output for FCR participation. The baseline inherits each interval's reference_point, which explicitly defines where the setpoint is measured.",
      "parameters": {
        "siteId": {
          "$ref": "#/components/parameters/siteId"
        }
      },
      "messages": {
        "batteryPowerCommand": {
          "$ref": "#/components/messages/BatteryPowerCommand"
        }
      }
    },
    "batteryFcrCommand": {
      "address": "cmd/{siteId}/battery/fcr",
      "title": "Battery FCR Offer Command Schedule",
      "description": "Topic for battery Frequency Containment Reserve offer command schedules",
      "parameters": {
        "siteId": {
          "$ref": "#/components/parameters/siteId"
        }
      },
      "messages": {
        "batteryFcrCommand": {
          "$ref": "#/components/messages/BatteryFcrCommand"
        }
      }
    },
    "commandResponse": {
      "address": "ack-cmd/{siteId}",
      "title": "Command Response",
      "description": "Channel for command execution response messages from EMS to Tensor Cloud",
      "parameters": {
        "siteId": {
          "$ref": "#/components/parameters/siteId"
        }
      },
      "messages": {
        "commandResponse": {
          "$ref": "#/components/messages/CommandResponse"
        }
      }
    },
    "alertEvent": {
      "address": "dt/{siteId}/{gatewayId}/alert",
      "title": "Alert Events",
      "summary": "State-change events for site alerts.",
      "parameters": {
        "siteId": {
          "$ref": "#/components/parameters/siteId"
        },
        "gatewayId": {
          "$ref": "#/components/parameters/gatewayId"
        }
      },
      "messages": {
        "alertEvent": {
          "$ref": "#/components/messages/AlertEvent"
        }
      }
    },
    "alertState": {
      "address": "dt/{siteId}/{gatewayId}/alert/active",
      "title": "Alert Snapshot",
      "summary": "Periodic snapshot of all currently active alerts.",
      "parameters": {
        "siteId": {
          "$ref": "#/components/parameters/siteId"
        },
        "gatewayId": {
          "$ref": "#/components/parameters/gatewayId"
        }
      },
      "messages": {
        "alertState": {
          "$ref": "#/components/messages/AlertState"
        }
      }
    },
    "telemetryFeedback": {
      "address": "ack-dt/{siteId}/{gatewayId}",
      "title": "Telemetry Feedback",
      "description": "Developer feedback channel for telemetry validation (testing environment only)",
      "servers": [
        {
          "$ref": "#/servers/testing"
        }
      ],
      "parameters": {
        "siteId": {
          "$ref": "#/components/parameters/siteId"
        },
        "gatewayId": {
          "$ref": "#/components/parameters/gatewayId"
        }
      },
      "messages": {
        "telemetryFeedback": {
          "$ref": "#/components/messages/TelemetryFeedback"
        }
      }
    }
  },
  "operations": {
    "sendLifetimeTelemetry": {
      "action": "send",
      "channel": {
        "$ref": "#/channels/lifetimeTelemetry"
      },
      "summary": "Send lifetime cumulative energy (kWh) telemetry data from the EMS device to Tensor Cloud. Tensor Cloud battery optimization requires either lifetime or windowed readings to be sent, ideally both.",
      "messages": [
        {
          "$ref": "#/channels/lifetimeTelemetry/messages/energyLifetime"
        }
      ]
    },
    "sendEnergyTelemetry": {
      "action": "send",
      "channel": {
        "$ref": "#/channels/energyTelemetry"
      },
      "summary": "Send windowed energy telemetry",
      "messages": [
        {
          "$ref": "#/channels/energyTelemetry/messages/energyWindowed"
        }
      ]
    },
    "sendPowerTelemetry": {
      "action": "send",
      "channel": {
        "$ref": "#/channels/powerTelemetry"
      },
      "summary": "Send instantaneous power telemetry",
      "messages": [
        {
          "$ref": "#/channels/powerTelemetry/messages/powerInstant"
        }
      ]
    },
    "sendBatteryState": {
      "action": "send",
      "channel": {
        "$ref": "#/channels/stateTelemetry"
      },
      "summary": "Send a single battery state measurement",
      "description": "EMS devices publish battery state metrics individually. The metric parameter in the channel determines which value is being sent.",
      "messages": [
        {
          "$ref": "#/channels/stateTelemetry/messages/batteryState"
        }
      ]
    },
    "sendCurtailment": {
      "action": "send",
      "channel": {
        "$ref": "#/channels/curtailment"
      },
      "summary": "Send curtailment telemetry",
      "description": "EMS sends curtailment schedule telemetry data to Tensor Cloud",
      "messages": [
        {
          "$ref": "#/channels/curtailment/messages/curtailment"
        }
      ]
    },
    "sendIrradiation": {
      "action": "send",
      "channel": {
        "$ref": "#/channels/irradiationTelemetry"
      },
      "summary": "Send solar irradiation telemetry",
      "description": "EMS sends on-site solar irradiation sensor readings to Tensor Cloud",
      "messages": [
        {
          "$ref": "#/channels/irradiationTelemetry/messages/irradiation"
        }
      ]
    },
    "sendSolarDc": {
      "action": "send",
      "channel": {
        "$ref": "#/channels/solarDcTelemetry"
      },
      "summary": "Send solar DC electrical telemetry",
      "description": "EMS sends the PV array's DC voltage and current to Tensor Cloud for DC-coupled sites",
      "messages": [
        {
          "$ref": "#/channels/solarDcTelemetry/messages/solarDcElectrical"
        }
      ]
    },
    "sendGridFrequency": {
      "action": "send",
      "channel": {
        "$ref": "#/channels/gridFrequencyTelemetry"
      },
      "summary": "Send grid frequency telemetry",
      "description": "EMS sends grid frequency readings measured at the grid connection point to Tensor Cloud. Mandatory at 1 Hz for the full duration of every FCR-awarded slot.",
      "messages": [
        {
          "$ref": "#/channels/gridFrequencyTelemetry/messages/gridFrequency"
        }
      ]
    },
    "sendPowerTelemetryBatch": {
      "action": "send",
      "channel": {
        "$ref": "#/channels/powerTelemetryBatch"
      },
      "summary": "Send batched instantaneous power telemetry",
      "description": "EMS sends many instantaneous power readings of a single metric in one message. Optional, and enabled per site by Tensor Cloud; backfill-only unless live batching has been granted for that site.",
      "messages": [
        {
          "$ref": "#/channels/powerTelemetryBatch/messages/powerInstantBatch"
        }
      ]
    },
    "sendGridFrequencyBatch": {
      "action": "send",
      "channel": {
        "$ref": "#/channels/gridFrequencyTelemetryBatch"
      },
      "summary": "Send batched grid frequency telemetry",
      "description": "EMS sends many grid frequency readings in one message. Optional, and enabled per site by Tensor Cloud; backfill-only unless live batching has been granted for that site.",
      "messages": [
        {
          "$ref": "#/channels/gridFrequencyTelemetryBatch/messages/gridFrequencyBatch"
        }
      ]
    },
    "receivePowerCommand": {
      "action": "receive",
      "channel": {
        "$ref": "#/channels/batteryPowerCommand"
      },
      "summary": "Receive charge/discharge commands",
      "description": "EMS gateway receives battery charge/discharge commands from Tensor Cloud",
      "messages": [
        {
          "$ref": "#/channels/batteryPowerCommand/messages/batteryPowerCommand"
        }
      ]
    },
    "receiveFcrCommand": {
      "action": "receive",
      "channel": {
        "$ref": "#/channels/batteryFcrCommand"
      },
      "summary": "Receive FCR offer commands",
      "description": "EMS gateway receives battery FCR (Frequency Containment Reserve) offer commands from Tensor Cloud",
      "messages": [
        {
          "$ref": "#/channels/batteryFcrCommand/messages/batteryFcrCommand"
        }
      ]
    },
    "sendCommandResponse": {
      "action": "send",
      "channel": {
        "$ref": "#/channels/commandResponse"
      },
      "summary": "Send command response",
      "description": "EMS sends command execution results to Tensor Cloud. Response topic MUST be within ack-cmd/{siteId} namespace. Messages with res_topic outside this namespace are rejected by AWS IoT policy.",
      "messages": [
        {
          "$ref": "#/channels/commandResponse/messages/commandResponse"
        }
      ]
    },
    "sendAlertEvent": {
      "action": "send",
      "channel": {
        "$ref": "#/channels/alertEvent"
      },
      "summary": "Send an alert state-change event",
      "messages": [
        {
          "$ref": "#/channels/alertEvent/messages/alertEvent"
        }
      ]
    },
    "sendAlertState": {
      "action": "send",
      "channel": {
        "$ref": "#/channels/alertState"
      },
      "summary": "Send current active-alert snapshot",
      "messages": [
        {
          "$ref": "#/channels/alertState/messages/alertState"
        }
      ]
    },
    "receiveTelemetryFeedback": {
      "action": "receive",
      "channel": {
        "$ref": "#/channels/telemetryFeedback"
      },
      "summary": "Receive telemetry feedback",
      "description": "In the testing environment, Tensor Cloud responds to every EMS telemetry message on this channel with an explicit `error` or `ok` acknowledgement. This developer feedback is ignored in production. Response topics are restricted to `ack-dt/{siteId}/{gatewayId}`.",
      "messages": [
        {
          "$ref": "#/channels/telemetryFeedback/messages/telemetryFeedback"
        }
      ]
    }
  },
  "components": {
    "parameters": {
      "siteId": {
        "description": "Site ID that uniquely identifies the site (grid connection point) optimized by Tensor Cloud.\n\nFormat: 'si_' prefix followed by 6 random lowercase alphanumeric characters.\nExample: si_qcf9gn\n\nCreated and shared by the Tensor Energy technical team during onboarding."
      },
      "gatewayId": {
        "description": "Gateway ID that uniquely identifies a specific deployed EMS device connected to Tensor Cloud.\nFormat: 'gw_' prefix followed by 6 random lowercase alphanumeric characters.\nExample: gw_0uv3tf\n\nCreated and shared by the Tensor Energy technical team during onboarding."
      },
      "powerMetric": {
        "description": "Metric name for instantaneous power (kW).\n\nRequired metrics depend on the use case. See the integration guide for valid combinations.",
        "enum": [
          "meter_export_ac",
          "meter_import_ac",
          "grid_to_load_ac",
          "load_demand_ac",
          "solar_net_generation_ac",
          "battery_charge_ac",
          "battery_discharge_ac",
          "solar_to_grid_ac",
          "solar_to_battery_ac",
          "solar_to_load_ac",
          "battery_to_grid_ac",
          "battery_to_load_ac",
          "grid_to_battery_ac",
          "solar_generation_dc",
          "battery_charge_dc",
          "battery_to_inverter_dc",
          "inverter_net_output_ac",
          "solar_to_battery_dc",
          "solar_to_inverter_dc",
          "inverter_to_battery_dc",
          "inverter_to_load_ac",
          "inverter_to_grid_ac",
          "grid_to_inverter_ac"
        ]
      },
      "energyMetric": {
        "description": "Metric name for aggregated energy (kWh). Required metrics depend on the use case; see the integration guide for details.",
        "enum": [
          "meter_export_ac",
          "meter_import_ac",
          "grid_to_load_ac",
          "load_demand_ac",
          "solar_net_generation_ac",
          "battery_charge_ac",
          "battery_discharge_ac",
          "solar_to_grid_ac",
          "solar_to_battery_ac",
          "solar_to_load_ac",
          "battery_to_grid_ac",
          "battery_to_load_ac",
          "grid_to_battery_ac",
          "solar_generation_dc",
          "battery_charge_dc",
          "battery_to_inverter_dc",
          "inverter_net_output_ac",
          "solar_to_battery_dc",
          "solar_to_inverter_dc",
          "inverter_to_battery_dc",
          "inverter_to_load_ac",
          "inverter_to_grid_ac",
          "grid_to_inverter_ac"
        ]
      },
      "lifetimeMetric": {
        "description": "Lifetime cumulative energy metric (kWh). Use an increasing counter; Tensor Cloud handles counter resets.",
        "enum": [
          "meter_export_ac",
          "meter_import_ac",
          "grid_to_load_ac",
          "load_demand_ac",
          "solar_net_generation_ac",
          "battery_charge_ac",
          "battery_discharge_ac",
          "solar_to_grid_ac",
          "solar_to_battery_ac",
          "solar_to_load_ac",
          "battery_to_grid_ac",
          "battery_to_load_ac",
          "grid_to_battery_ac",
          "solar_generation_dc",
          "battery_charge_dc",
          "battery_to_inverter_dc",
          "inverter_net_output_ac",
          "solar_to_battery_dc",
          "solar_to_inverter_dc",
          "inverter_to_battery_dc",
          "inverter_to_load_ac",
          "inverter_to_grid_ac",
          "grid_to_inverter_ac"
        ]
      },
      "window": {
        "description": "Aggregation window length as ISO 8601 duration (e.g., PT5M = 5 minutes)",
        "enum": ["PT1M", "PT5M", "PT10M", "PT15M", "PT30M", "PT1H"]
      },
      "batteryStateMetric": {
        "description": "Battery state metrics. Battery state of energy (SoE) and remaining energy are required at a minimum. Remaining energy is the battery's capacity in kWh at nominal temperature, including degradation effects.\n\n- battery_soe: Energy currently stored in the battery (the kWh equivalent of State of Charge). Update frequently. Valid range: 0 ≤ battery_soe ≤ battery_energy_remaining.\n- battery_energy_remaining: Maximum usable battery capacity at nominal temperature (~25°C), accounting for degradation and faulty cells. Update infrequently, typically once per day.",
        "enum": ["battery_soe", "battery_energy_remaining"]
      }
    },
    "messages": {
      "EnergyLifetime": {
        "name": "EnergyLifetime",
        "title": "Lifetime Energy Reading Telemetry",
        "summary": "Telemetry message for cumulative lifetime energy in kWh.",
        "correlationId": {
          "description": "Correlation based on message_id for tracing",
          "location": "$message.payload#/message_id"
        },
        "contentType": "application/json",
        "payload": {
          "$schema": "http://json-schema.org/draft-07/schema#",
          "type": "object",
          "properties": {
            "schema_version": {
              "description": "Version of the schema for safe evolution and backward compatibility",
              "type": "string",
              "default": "2.9.0",
              "pattern": "^\\d+\\.\\d+\\.\\d+$",
              "examples": ["2.9.0"]
            },
            "message_id": {
              "description": "ID of the message in prefixed UUID format",
              "type": "string",
              "pattern": "^msg_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "examples": ["msg_a80dd197-47fa-44ca-8966-40d7b4d88277"]
            },
            "measurement_ts": {
              "description": "ISO-8601 compliant timestamp of when the telemetry was measured by the EMS",
              "type": "string",
              "format": "date-time",
              "examples": ["2024-01-04T03:26:10.000+09:00"]
            },
            "measurement_value": {
              "description": "Lifetime cumulative energy measurement",
              "type": "object",
              "properties": {
                "value": {
                  "description": "Total cumulative energy since commissioning. Use a counter that only increases, like an electricity meter. Tensor Cloud handles counter resets.",
                  "type": "number",
                  "minimum": 0
                },
                "unit": {
                  "description": "Unit of measurement",
                  "type": "string",
                  "enum": ["kWh"]
                }
              },
              "required": ["value", "unit"],
              "additionalProperties": false
            }
          },
          "additionalProperties": true,
          "required": ["message_id", "measurement_ts", "measurement_value", "schema_version"]
        }
      },
      "EnergyWindowed": {
        "name": "EnergyWindowed",
        "title": "Windowed Energy Reading Telemetry",
        "summary": "Telemetry message for energy aggregated over a time window",
        "correlationId": {
          "description": "Correlation based on message_id for tracing",
          "location": "$message.payload#/message_id"
        },
        "contentType": "application/json",
        "payload": {
          "$schema": "http://json-schema.org/draft-07/schema#",
          "type": "object",
          "properties": {
            "schema_version": {
              "description": "Version of the schema for safe evolution and backward compatibility",
              "type": "string",
              "default": "2.9.0",
              "pattern": "^\\d+\\.\\d+\\.\\d+$",
              "examples": ["2.9.0"]
            },
            "message_id": {
              "description": "ID of the message in prefixed UUID format",
              "type": "string",
              "pattern": "^msg_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "examples": ["msg_a80dd197-47fa-44ca-8966-40d7b4d88277"]
            },
            "measurement_ts": {
              "description": "ISO-8601 compliant timestamp set by the EMS. For this windowed measurement, it must equal measurement_value.start_ts and identify the start of the window, not its end or midpoint.",
              "type": "string",
              "format": "date-time",
              "examples": ["2024-01-04T03:00:00.000+09:00"]
            },
            "measurement_value": {
              "description": "Energy measured during the window. The interval includes start_ts and excludes end_ts. For example, an end_ts of 10:30 includes measurements through 10:29:59.999... but excludes 10:30.",
              "type": "object",
              "properties": {
                "start_ts": {
                  "description": "ISO-8601 compliant timestamp for window start (inclusive)",
                  "type": "string",
                  "format": "date-time",
                  "examples": ["2024-01-04T03:00:00.000+09:00"]
                },
                "end_ts": {
                  "description": "ISO-8601 compliant timestamp for window end (exclusive)",
                  "type": "string",
                  "format": "date-time",
                  "examples": ["2024-01-04T03:30:00.000+09:00"]
                },
                "value": {
                  "description": "Total energy measured within the window",
                  "type": "number",
                  "minimum": 0
                },
                "unit": {
                  "description": "Unit of measurement",
                  "type": "string",
                  "enum": ["kWh"]
                }
              },
              "required": ["start_ts", "end_ts", "value", "unit"],
              "additionalProperties": false
            }
          },
          "additionalProperties": true,
          "required": ["message_id", "measurement_ts", "measurement_value", "schema_version"]
        }
      },
      "PowerInstant": {
        "name": "PowerInstant",
        "title": "Instantaneous Power Reading Telemetry",
        "summary": "Telemetry message for instantaneous power measurement",
        "correlationId": {
          "description": "Correlation based on message_id for tracing",
          "location": "$message.payload#/message_id"
        },
        "contentType": "application/json",
        "payload": {
          "$schema": "http://json-schema.org/draft-07/schema#",
          "type": "object",
          "properties": {
            "schema_version": {
              "description": "Version of the schema for safe evolution and backward compatibility",
              "type": "string",
              "default": "2.9.0",
              "pattern": "^\\d+\\.\\d+\\.\\d+$",
              "examples": ["2.9.0"]
            },
            "message_id": {
              "description": "ID of the message in prefixed UUID format",
              "type": "string",
              "pattern": "^msg_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "examples": ["msg_a80dd197-47fa-44ca-8966-40d7b4d88277"]
            },
            "measurement_ts": {
              "description": "ISO-8601 compliant timestamp set by the EMS. For 'instant' readings, this is the sampling time. For windowed ('average') readings, it must identify the start of aggregation_window, not its end or midpoint.",
              "type": "string",
              "format": "date-time",
              "examples": ["2024-01-04T03:26:10.000+09:00"]
            },
            "pre_qualification": {
              "description": "Optional. Set to true on telemetry the EMS emits while in FCR pre-qualification (事前審査) test mode, a mode activated by the EMS operator, not by Tensor Cloud. Applies only to instantaneous-power and grid-frequency telemetry. Omit (or set false) during normal operation. Tensor Cloud uses it to exclude that window from optimization and from FCR Assessment II submissions.",
              "type": "boolean",
              "default": false
            },
            "measurement_value": {
              "description": "Instantaneous power measurement",
              "type": "object",
              "properties": {
                "value": {
                  "description": "Power value. An instantaneous reading at measurement_ts when aggregation is 'instant'; the mean over aggregation_window beginning at measurement_ts when aggregation is 'average'.",
                  "type": "number",
                  "minimum": 0
                },
                "unit": {
                  "description": "Unit of measurement",
                  "type": "string",
                  "enum": ["kW"]
                },
                "aggregation": {
                  "description": "Whether 'value' is an instantaneous point sample at measurement_ts ('instant', the default) or the mean over the interval beginning at measurement_ts ('average'). For offline FCR Assessment II, publish 'average' over a 1-second window.",
                  "type": "string",
                  "enum": ["instant", "average"],
                  "default": "instant"
                },
                "aggregation_window": {
                  "description": "ISO-8601 duration over which an 'average' value was computed, e.g. 'PT1S'. Required when aggregation is 'average'; omit when 'instant'.",
                  "type": "string",
                  "examples": ["PT1S"]
                }
              },
              "required": ["value", "unit"],
              "additionalProperties": false,
              "if": {
                "properties": { "aggregation": { "const": "average" } },
                "required": ["aggregation"]
              },
              "then": {
                "required": ["aggregation_window"]
              }
            }
          },
          "additionalProperties": true,
          "required": ["message_id", "measurement_ts", "measurement_value", "schema_version"]
        },
        "examples": [
          {
            "name": "FCR Assessment II 1-second average power sample",
            "payload": {
              "schema_version": "2.9.0",
              "message_id": "msg_a80dd197-47fa-44ca-8966-40d7b4d88277",
              "measurement_ts": "2024-01-04T03:00:00.000+09:00",
              "measurement_value": {
                "value": 1480.5,
                "unit": "kW",
                "aggregation": "average",
                "aggregation_window": "PT1S"
              }
            }
          },
          {
            "name": "Pre-qualification (事前審査) power sample",
            "summary": "Same 1 Hz power telemetry as a real FCR slot, flagged pre_qualification: true while the EMS operator has the site in pre-qualification test mode.",
            "payload": {
              "schema_version": "2.9.0",
              "message_id": "msg_b91ee208-58fb-45db-9a77-51e8c5e99388",
              "measurement_ts": "2024-01-04T03:00:00.000+09:00",
              "pre_qualification": true,
              "measurement_value": {
                "value": 1480.5,
                "unit": "kW",
                "aggregation": "average",
                "aggregation_window": "PT1S"
              }
            }
          }
        ]
      },
      "Curtailment": {
        "name": "Curtailment",
        "title": "Curtailment Schedule Telemetry",
        "summary": "Telemetry message for curtailment schedules. Required for every topology, including stand-alone battery systems: a site without co-located solar can still be subject to TSO output control. See the integration guide's 'Common to all topologies' telemetry table.",
        "correlationId": {
          "description": "Correlation based on message_id for tracing",
          "location": "$message.payload#/message_id"
        },
        "contentType": "application/json",
        "payload": {
          "$schema": "http://json-schema.org/draft-07/schema#",
          "type": "object",
          "properties": {
            "schema_version": {
              "description": "Version of the schema for safe evolution and backward compatibility",
              "type": "string",
              "default": "2.9.0",
              "pattern": "^\\d+\\.\\d+\\.\\d+$",
              "examples": ["2.9.0"]
            },
            "message_id": {
              "description": "ID of the message in prefixed UUID format",
              "type": "string",
              "pattern": "^msg_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "examples": ["msg_a80dd197-47fa-44ca-8966-40d7b4d88277"]
            },
            "measurement_ts": {
              "description": "ISO-8601 compliant timestamp for the curtailment schedule. Use the schedule creation time issued by the TSO when available; otherwise use the time the EMS received the schedule from the TSO. This is not a windowed measurement. The covered periods are defined by measurement_value[].start_ts and measurement_value[].end_ts.",
              "type": "string",
              "format": "date-time",
              "examples": ["2024-01-04T03:26:10.000+09:00"]
            },
            "measurement_value": {
              "description": "Curtailment schedule",
              "type": "array",
              "minItems": 1,
              "items": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "start_ts": {
                    "description": "ISO-8601 compliant timestamp indicating the start time of the curtailment event. Inclusive to the left. Has to be before `end_ts`, and has to fall on a 30-minute slot boundary: minute `00` or `30` in JST, with zero seconds.",
                    "type": "string",
                    "format": "date-time",
                    "examples": ["2024-01-04T14:00:00.000+09:00"]
                  },
                  "end_ts": {
                    "description": "ISO-8601 compliant timestamp indicating the end time of the curtailment event. Exclusive to the right. Has to be after `start_ts`, on the same 30-minute slot boundaries, and exactly 30 minutes after it. A curtailment covering a longer period is published as consecutive 30-minute intervals, not as one long interval. An interval that is off boundary or not exactly 30 minutes long is rejected with `TIME_WINDOW_INVALID`.",
                    "type": "string",
                    "format": "date-time",
                    "examples": ["2024-01-04T14:30:00.000+09:00"]
                  },
                  "limit_percent": {
                    "description": "Percentage of maximum site output permitted by the TSO. A value of 0 means full curtailment with no site output. Valid range: [0,100].",
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 100
                  }
                },
                "required": ["start_ts", "end_ts", "limit_percent"]
              }
            }
          },
          "additionalProperties": true,
          "required": ["message_id", "measurement_ts", "measurement_value", "schema_version"]
        }
      },
      "Irradiation": {
        "name": "Irradiation",
        "title": "Solar Irradiation Telemetry",
        "summary": "Telemetry message for on-site solar irradiation sensor readings in kW/m2",
        "correlationId": {
          "description": "Correlation based on message_id for tracing",
          "location": "$message.payload#/message_id"
        },
        "contentType": "application/json",
        "payload": {
          "$schema": "http://json-schema.org/draft-07/schema#",
          "type": "object",
          "properties": {
            "schema_version": {
              "description": "Version of the schema for safe evolution and backward compatibility",
              "type": "string",
              "default": "2.9.0",
              "pattern": "^\\d+\\.\\d+\\.\\d+$",
              "examples": ["2.9.0"]
            },
            "message_id": {
              "description": "ID of the message in prefixed UUID format",
              "type": "string",
              "pattern": "^msg_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "examples": ["msg_a80dd197-47fa-44ca-8966-40d7b4d88277"]
            },
            "measurement_ts": {
              "description": "ISO-8601 compliant timestamp of when the telemetry was measured by the EMS",
              "type": "string",
              "format": "date-time",
              "examples": ["2024-01-04T03:26:10.000+09:00"]
            },
            "measurement_value": {
              "description": "Solar irradiation measurement from on-site sensor",
              "type": "object",
              "properties": {
                "value": {
                  "description": "Instantaneous solar irradiation reading",
                  "type": "number",
                  "minimum": 0
                },
                "unit": {
                  "description": "Unit of measurement",
                  "type": "string",
                  "enum": ["kW/m2"]
                }
              },
              "required": ["value", "unit"],
              "additionalProperties": false
            }
          },
          "additionalProperties": true,
          "required": ["message_id", "measurement_ts", "measurement_value", "schema_version"]
        },
        "examples": [
          {
            "name": "Solar irradiation reading example",
            "payload": {
              "schema_version": "2.9.0",
              "message_id": "msg_a80dd197-47fa-44ca-8966-40d7b4d88277",
              "measurement_ts": "2024-01-04T12:30:00.000+09:00",
              "measurement_value": {
                "value": 0.845,
                "unit": "kW/m2"
              }
            }
          }
        ]
      },
      "SolarDcElectrical": {
        "name": "SolarDcElectrical",
        "title": "Solar DC Electrical Telemetry",
        "summary": "Telemetry message for array-aggregate PV DC voltage (V) and current (A), reported as a matched pair for DC-coupled PV plus battery sites.",
        "correlationId": {
          "description": "Correlation based on message_id for tracing",
          "location": "$message.payload#/message_id"
        },
        "contentType": "application/json",
        "payload": {
          "$schema": "http://json-schema.org/draft-07/schema#",
          "type": "object",
          "properties": {
            "schema_version": {
              "description": "Version of the schema for safe evolution and backward compatibility",
              "type": "string",
              "default": "2.9.0",
              "pattern": "^\\d+\\.\\d+\\.\\d+$",
              "examples": ["2.9.0"]
            },
            "message_id": {
              "description": "ID of the message in prefixed UUID format",
              "type": "string",
              "pattern": "^msg_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "examples": ["msg_a80dd197-47fa-44ca-8966-40d7b4d88277"]
            },
            "measurement_ts": {
              "description": "ISO-8601 compliant timestamp of when the telemetry was measured by the EMS. The voltage and current in this message must be sampled at this same instant.",
              "type": "string",
              "format": "date-time",
              "examples": ["2024-01-04T03:26:10.000+09:00"]
            },
            "measurement_value": {
              "description": "PV-array DC voltage and current measured at the DC link, upstream of the inverter, as a single aggregate across the whole array. Voltage and current are sampled at the same instant and form one matched pair.",
              "type": "object",
              "properties": {
                "voltage": {
                  "description": "Instantaneous PV-array DC voltage at the DC link, aggregate across the array",
                  "type": "object",
                  "properties": {
                    "value": {
                      "description": "DC voltage reading in volts",
                      "type": "number",
                      "minimum": 0
                    },
                    "unit": {
                      "description": "Unit of measurement",
                      "type": "string",
                      "enum": ["V"]
                    }
                  },
                  "required": ["value", "unit"],
                  "additionalProperties": false
                },
                "current": {
                  "description": "Instantaneous PV-array DC current at the DC link, aggregate across the array, sampled at the same instant as voltage",
                  "type": "object",
                  "properties": {
                    "value": {
                      "description": "DC current reading in amperes",
                      "type": "number",
                      "minimum": 0
                    },
                    "unit": {
                      "description": "Unit of measurement",
                      "type": "string",
                      "enum": ["A"]
                    }
                  },
                  "required": ["value", "unit"],
                  "additionalProperties": false
                }
              },
              "required": ["voltage", "current"],
              "additionalProperties": false
            }
          },
          "additionalProperties": true,
          "required": ["message_id", "measurement_ts", "measurement_value", "schema_version"]
        },
        "examples": [
          {
            "name": "Solar DC electrical reading example",
            "payload": {
              "schema_version": "2.9.0",
              "message_id": "msg_a80dd197-47fa-44ca-8966-40d7b4d88277",
              "measurement_ts": "2024-01-04T12:30:00.000+09:00",
              "measurement_value": {
                "voltage": {
                  "value": 812.4,
                  "unit": "V"
                },
                "current": {
                  "value": 143.2,
                  "unit": "A"
                }
              }
            }
          }
        ]
      },
      "GridFrequency": {
        "name": "GridFrequency",
        "title": "Grid Frequency Telemetry",
        "summary": "Telemetry message for grid frequency measured at the grid connection point, in Hz. Mandatory at 1 Hz for the duration of every FCR-awarded slot.",
        "correlationId": {
          "description": "Correlation based on message_id for tracing",
          "location": "$message.payload#/message_id"
        },
        "contentType": "application/json",
        "payload": {
          "$schema": "http://json-schema.org/draft-07/schema#",
          "type": "object",
          "properties": {
            "schema_version": {
              "description": "Version of the schema for safe evolution and backward compatibility",
              "type": "string",
              "default": "2.9.0",
              "pattern": "^\\d+\\.\\d+\\.\\d+$",
              "examples": ["2.9.0"]
            },
            "message_id": {
              "description": "ID of the message in prefixed UUID format",
              "type": "string",
              "pattern": "^msg_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "examples": ["msg_a80dd197-47fa-44ca-8966-40d7b4d88277"]
            },
            "measurement_ts": {
              "description": "ISO-8601 compliant timestamp set by the EMS. For 'instant' readings, this is the sampling time. For windowed ('average') readings, it must identify the start of aggregation_window, not its end or midpoint.",
              "type": "string",
              "format": "date-time",
              "examples": ["2024-01-04T03:26:10.000+09:00"]
            },
            "pre_qualification": {
              "description": "Optional. Set to true on telemetry the EMS emits while in FCR pre-qualification (事前審査) test mode, a mode activated by the EMS operator, not by Tensor Cloud. Applies only to instantaneous-power and grid-frequency telemetry. Omit (or set false) during normal operation. Tensor Cloud uses it to exclude that window from optimization and from FCR Assessment II submissions.",
              "type": "boolean",
              "default": false
            },
            "measurement_value": {
              "description": "Grid frequency measurement",
              "type": "object",
              "properties": {
                "value": {
                  "description": "Grid frequency in Hz, measured at the grid connection point from the live AC voltage waveform. Report to at least 0.01 Hz resolution; do not publish a nominal, scheduled, or otherwise derived value. An instantaneous reading at measurement_ts when aggregation is 'instant'; the mean over aggregation_window beginning at measurement_ts when aggregation is 'average'. For FCR-awarded slots, publish 'average' over a 1-second window at 1 Hz.",
                  "type": "number",
                  "minimum": 0
                },
                "unit": {
                  "description": "Unit of measurement",
                  "type": "string",
                  "enum": ["Hz"]
                },
                "aggregation": {
                  "description": "Whether 'value' is an instantaneous point sample at measurement_ts ('instant', the default) or the mean over the interval beginning at measurement_ts ('average'). For FCR-awarded slots, publish 'average' over a 1-second window.",
                  "type": "string",
                  "enum": ["instant", "average"],
                  "default": "instant"
                },
                "aggregation_window": {
                  "description": "ISO-8601 duration over which an 'average' value was computed, e.g. 'PT1S'. Required when aggregation is 'average'; omit when 'instant'.",
                  "type": "string",
                  "examples": ["PT1S"]
                }
              },
              "required": ["value", "unit"],
              "additionalProperties": false,
              "if": {
                "properties": { "aggregation": { "const": "average" } },
                "required": ["aggregation"]
              },
              "then": {
                "required": ["aggregation_window"]
              }
            }
          },
          "additionalProperties": true,
          "required": ["message_id", "measurement_ts", "measurement_value", "schema_version"]
        },
        "examples": [
          {
            "name": "Grid frequency reading example (FCR-awarded slot)",
            "payload": {
              "schema_version": "2.9.0",
              "message_id": "msg_a80dd197-47fa-44ca-8966-40d7b4d88277",
              "measurement_ts": "2024-01-04T12:30:00.000+09:00",
              "measurement_value": {
                "value": 50.0123,
                "unit": "Hz",
                "aggregation": "average",
                "aggregation_window": "PT1S"
              }
            }
          },
          {
            "name": "Pre-qualification (事前審査) grid frequency sample",
            "summary": "During pre-qualification the frequency is the simulated signal the EMS drives the battery against; it is published the same way but flagged pre_qualification: true.",
            "payload": {
              "schema_version": "2.9.0",
              "message_id": "msg_b91ee208-58fb-45db-9a77-51e8c5e99388",
              "measurement_ts": "2024-01-04T12:30:00.000+09:00",
              "pre_qualification": true,
              "measurement_value": {
                "value": 49.812,
                "unit": "Hz",
                "aggregation": "average",
                "aggregation_window": "PT1S"
              }
            }
          }
        ]
      },
      "PowerInstantBatch": {
        "name": "PowerInstantBatch",
        "title": "Batched Instantaneous Power Telemetry",
        "summary": "Many instantaneous power measurements of a single metric in one message",
        "correlationId": {
          "description": "Correlation based on message_id for tracing",
          "location": "$message.payload#/message_id"
        },
        "contentType": "application/json",
        "payload": {
          "$schema": "http://json-schema.org/draft-07/schema#",
          "type": "object",
          "properties": {
            "schema_version": {
              "description": "Version of the schema for safe evolution and backward compatibility",
              "type": "string",
              "default": "2.9.0",
              "pattern": "^\\d+\\.\\d+\\.\\d+$",
              "examples": ["2.9.0"]
            },
            "message_id": {
              "description": "ID of this batch message in prefixed UUID format. It identifies the publish attempt, not the measurements. A resend may regroup the same measurements into a different batch size. Tensor Cloud deduplicates each measurement by its own message_id.",
              "type": "string",
              "pattern": "^msg_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "examples": ["msg_a80dd197-47fa-44ca-8966-40d7b4d88277"]
            },
            "unit": {
              "description": "Unit of measurement, applying to every entry in 'measurements'",
              "type": "string",
              "enum": ["kW"]
            },
            "aggregation": {
              "description": "Whether each 'value' is an instantaneous point sample at its 'measurement_ts' ('instant', the default) or the mean over the interval beginning at it ('average'). Applies to every entry in 'measurements'; split the batch when it changes. For offline FCR Assessment II, publish 'average' over a 1-second window.",
              "type": "string",
              "enum": ["instant", "average"],
              "default": "instant"
            },
            "aggregation_window": {
              "description": "ISO-8601 duration over which each 'average' value was computed, e.g. 'PT1S'. Required when aggregation is 'average'; omit when 'instant'. Applies to every entry in 'measurements'; split the batch when it changes.",
              "type": "string",
              "examples": ["PT1S"]
            },
            "pre_qualification": {
              "description": "Optional. Set to true while the EMS is in FCR pre-qualification (事前審査) test mode. The EMS operator, rather than Tensor Cloud, activates this mode. The value applies to every entry in 'measurements', so split a batch when the operator enters or leaves the mode. Omit it or set it to false during normal operation.",
              "type": "boolean",
              "default": false
            },
            "measurements": {
              "description": "Readings in strictly ascending 'measurement_ts' order, with no repeated timestamp or message_id. Omit any second without a reliable reading; do not publish 0 or null in its place. Tensor Cloud accepts or rejects the whole batch.",
              "type": "array",
              "minItems": 1,
              "maxItems": 900,
              "items": {
                "type": "object",
                "properties": {
                  "message_id": {
                    "description": "ID of this individual measurement in prefixed UUID format. This is the deduplication key: reuse the original value whenever the same reading is resent, in either the batched or the single-message form.",
                    "type": "string",
                    "pattern": "^msg_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
                    "examples": ["msg_b91ee208-58fb-45db-9a77-51e8c5e99388"]
                  },
                  "measurement_ts": {
                    "description": "ISO-8601 compliant timestamp set by the EMS. For 'instant' readings, this is the sampling time. For 'average' readings, it must identify the start of aggregation_window, not its end or midpoint.",
                    "type": "string",
                    "format": "date-time",
                    "examples": ["2024-01-04T03:00:00.000+09:00"]
                  },
                  "value": {
                    "description": "Power value in the batch's 'unit'. An instantaneous reading at 'measurement_ts' when aggregation is 'instant'; the mean over the window beginning at it when 'average'.",
                    "type": "number",
                    "minimum": 0
                  }
                },
                "required": ["message_id", "measurement_ts", "value"],
                "additionalProperties": false
              }
            }
          },
          "additionalProperties": true,
          "required": ["schema_version", "message_id", "unit", "measurements"],
          "if": {
            "properties": {
              "aggregation": {
                "const": "average"
              }
            },
            "required": ["aggregation"]
          },
          "then": {
            "required": ["aggregation_window"]
          }
        },
        "examples": [
          {
            "name": "One minute of FCR Assessment II 1-second average power",
            "summary": "Sixty 1-second averages in a single message. The second at 03:00:02 is absent because no reliable reading was available for it",
            "payload": {
              "schema_version": "2.9.0",
              "message_id": "msg_a80dd197-47fa-44ca-8966-40d7b4d88277",
              "unit": "kW",
              "aggregation": "average",
              "aggregation_window": "PT1S",
              "measurements": [
                {
                  "message_id": "msg_b91ee208-58fb-45db-9a77-51e8c5e99388",
                  "measurement_ts": "2024-01-04T03:00:00.000+09:00",
                  "value": 1480.5
                },
                {
                  "message_id": "msg_c02ff319-69ac-46ec-ab88-62f9d6faa499",
                  "measurement_ts": "2024-01-04T03:00:01.000+09:00",
                  "value": 1481.2
                },
                {
                  "message_id": "msg_d13aa420-7abd-4711-bc99-73fae7fbb500",
                  "measurement_ts": "2024-01-04T03:00:03.000+09:00",
                  "value": 1479.8
                }
              ]
            }
          }
        ]
      },
      "GridFrequencyBatch": {
        "name": "GridFrequencyBatch",
        "title": "Batched Grid Frequency Telemetry",
        "summary": "Many grid frequency measurements in one message",
        "correlationId": {
          "description": "Correlation based on message_id for tracing",
          "location": "$message.payload#/message_id"
        },
        "contentType": "application/json",
        "payload": {
          "$schema": "http://json-schema.org/draft-07/schema#",
          "type": "object",
          "properties": {
            "schema_version": {
              "description": "Version of the schema for safe evolution and backward compatibility",
              "type": "string",
              "default": "2.9.0",
              "pattern": "^\\d+\\.\\d+\\.\\d+$",
              "examples": ["2.9.0"]
            },
            "message_id": {
              "description": "ID of this batch message in prefixed UUID format. It identifies the publish attempt, not the measurements. A resend may regroup the same measurements into a different batch size. Tensor Cloud deduplicates each measurement by its own message_id.",
              "type": "string",
              "pattern": "^msg_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "examples": ["msg_a80dd197-47fa-44ca-8966-40d7b4d88277"]
            },
            "unit": {
              "description": "Unit of measurement, applying to every entry in 'measurements'",
              "type": "string",
              "enum": ["Hz"]
            },
            "aggregation": {
              "description": "Whether each 'value' is an instantaneous point sample at its 'measurement_ts' ('instant', the default) or the mean over the interval beginning at it ('average'). Applies to every entry in 'measurements'; split the batch when it changes. For FCR-awarded slots, publish 'average' over a 1-second window.",
              "type": "string",
              "enum": ["instant", "average"],
              "default": "instant"
            },
            "aggregation_window": {
              "description": "ISO-8601 duration over which each 'average' value was computed, e.g. 'PT1S'. Required when aggregation is 'average'; omit when 'instant'. Applies to every entry in 'measurements'; split the batch when it changes.",
              "type": "string",
              "examples": ["PT1S"]
            },
            "pre_qualification": {
              "description": "Optional. Set to true while the EMS is in FCR pre-qualification (事前審査) test mode. The EMS operator, rather than Tensor Cloud, activates this mode. The value applies to every entry in 'measurements', so split a batch when the operator enters or leaves the mode. Omit it or set it to false during normal operation.",
              "type": "boolean",
              "default": false
            },
            "measurements": {
              "description": "Readings in strictly ascending 'measurement_ts' order, with no repeated timestamp or message_id. Omit any second without a reliable reading; do not publish 0 or null in its place. Tensor Cloud accepts or rejects the whole batch.",
              "type": "array",
              "minItems": 1,
              "maxItems": 900,
              "items": {
                "type": "object",
                "properties": {
                  "message_id": {
                    "description": "ID of this individual measurement in prefixed UUID format. This is the deduplication key: reuse the original value whenever the same reading is resent, in either the batched or the single-message form.",
                    "type": "string",
                    "pattern": "^msg_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
                    "examples": ["msg_c02ff319-69ac-46ec-ab88-62f9d6faa499"]
                  },
                  "measurement_ts": {
                    "description": "ISO-8601 compliant timestamp set by the EMS. For 'instant' readings, this is the sampling time. For 'average' readings, it must identify the start of aggregation_window, not its end or midpoint.",
                    "type": "string",
                    "format": "date-time",
                    "examples": ["2024-01-04T03:00:00.000+09:00"]
                  },
                  "value": {
                    "description": "Grid frequency in Hz, measured at the grid connection point from the live AC voltage waveform. Report to at least 0.01 Hz resolution; do not publish a nominal, scheduled, or otherwise derived value.",
                    "type": "number",
                    "minimum": 0
                  }
                },
                "required": ["message_id", "measurement_ts", "value"],
                "additionalProperties": false
              }
            }
          },
          "additionalProperties": true,
          "required": ["schema_version", "message_id", "unit", "measurements"],
          "if": {
            "properties": {
              "aggregation": {
                "const": "average"
              }
            },
            "required": ["aggregation"]
          },
          "then": {
            "required": ["aggregation_window"]
          }
        },
        "examples": [
          {
            "name": "Batched 1-second average grid frequency",
            "summary": "Frequency samples time-aligned with the power samples for the same seconds",
            "payload": {
              "schema_version": "2.9.0",
              "message_id": "msg_e24bb531-8bce-4822-adaa-840bc8fcc611",
              "unit": "Hz",
              "aggregation": "average",
              "aggregation_window": "PT1S",
              "measurements": [
                {
                  "message_id": "msg_f45cc642-90de-4933-bfbb-951ca7edd722",
                  "measurement_ts": "2024-01-04T03:00:00.000+09:00",
                  "value": 49.812
                },
                {
                  "message_id": "msg_0561dd53-a1ef-4a44-cadd-a62bd9fdd833",
                  "measurement_ts": "2024-01-04T03:00:01.000+09:00",
                  "value": 49.987
                }
              ]
            }
          }
        ]
      },
      "BatteryStateValue": {
        "name": "BatteryStateValue",
        "title": "Battery state telemetry",
        "summary": "Single battery state measurement",
        "correlationId": {
          "description": "Correlation based on message_id for tracing",
          "location": "$message.payload#/message_id"
        },
        "contentType": "application/json",
        "payload": {
          "$schema": "http://json-schema.org/draft-07/schema#",
          "type": "object",
          "properties": {
            "schema_version": {
              "description": "Version of the schema for safe evolution and backward compatibility",
              "type": "string",
              "default": "2.9.0",
              "pattern": "^\\d+\\.\\d+\\.\\d+$",
              "examples": ["2.9.0"]
            },
            "message_id": {
              "description": "ID of the message in prefixed UUID format",
              "type": "string",
              "pattern": "^msg_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "examples": ["msg_a80dd197-47fa-44ca-8966-40d7b4d88277"]
            },
            "measurement_ts": {
              "description": "ISO-8601 compliant timestamp of when the telemetry was measured by the EMS",
              "type": "string",
              "format": "date-time",
              "examples": ["2024-01-04T03:26:10.000+09:00"]
            },
            "measurement_value": {
              "type": "object",
              "properties": {
                "value": {
                  "type": "number",
                  "minimum": 0
                },
                "unit": {
                  "type": "string",
                  "enum": ["kWh"]
                }
              },
              "additionalProperties": false,
              "required": ["value", "unit"]
            }
          },
          "additionalProperties": true,
          "required": ["message_id", "measurement_ts", "measurement_value", "schema_version"]
        }
      },
      "BatteryPowerCommand": {
        "name": "BatteryPowerCommand",
        "title": "Battery Power Setpoint Command",
        "summary": "Tensor Cloud command that controls battery charging and discharging through a schedule of arbitrary time intervals.",
        "correlationId": {
          "description": "Correlation based on message_id for tracing",
          "location": "$message.payload#/message_id"
        },
        "contentType": "application/json",
        "payload": {
          "$schema": "http://json-schema.org/draft-07/schema#",
          "type": "object",
          "properties": {
            "schema_version": {
              "description": "Version of the schema for safe evolution and backward compatibility",
              "type": "string",
              "default": "2.9.0",
              "pattern": "^\\d+\\.\\d+\\.\\d+$",
              "examples": ["2.9.0"]
            },
            "message_id": {
              "description": "ID of the message in prefixed UUID format",
              "type": "string",
              "pattern": "^msg_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "examples": ["msg_a80dd197-47fa-44ca-8966-40d7b4d88277"]
            },
            "issue_ts": {
              "description": "ISO-8601 compliant timestamp of when the command was issued by Tensor Cloud. Used by EMS for prioritization policy execution.",
              "type": "string",
              "format": "date-time",
              "examples": ["2024-01-05T00:00:00.000+09:00"]
            },
            "res_topic": {
              "description": "Response topic where EMS will send command results. Must be within ack-cmd/{siteId} namespace. Messages with res_topic outside this namespace are rejected by the MQTT broker.",
              "type": "string",
              "pattern": "^ack-cmd/si_[a-z0-9]{6}$",
              "examples": ["ack-cmd/si_qcf9gn"]
            },
            "control": {
              "description": "Control command",
              "type": "object",
              "properties": {
                "priority": {
                  "description": "Control command priority. Larger values have higher priority; for example, 50 has higher priority than 10.",
                  "type": "number",
                  "minimum": 0,
                  "maximum": 100
                },
                "schedule": {
                  "description": "Battery charge/discharge schedule",
                  "type": "array",
                  "minItems": 1,
                  "items": {
                    "type": "object",
                    "properties": {
                      "start_ts": {
                        "description": "ISO-8601 compliant timestamp indicating the start time of the scheduled charge/discharge event. Inclusive to the left",
                        "type": "string",
                        "format": "date-time",
                        "examples": ["2024-01-05T00:00:00.000+09:00"]
                      },
                      "end_ts": {
                        "description": "ISO-8601 compliant timestamp indicating the end time of the scheduled charge/discharge event. Exclusive to the right",
                        "type": "string",
                        "format": "date-time",
                        "examples": ["2024-01-05T00:30:00.000+09:00"]
                      },
                      "power_kw": {
                        "description": "Charge or discharge power in kilowatts. Positive values charge the battery, negative values discharge it, and 0 places it on standby. The interval's 'reference_point' defines where this value is measured; the site's topology does not determine it.",
                        "type": "number",
                        "examples": [10.123, -5.5, 0]
                      },
                      "reference_point": {
                        "description": "Defines where 'power_kw' is measured for this interval. The field defaults to 'battery' when omitted. With 'battery', the EMS targets the battery itself and leaves any co-located generation unchanged; 0 means the battery stands by. With 'grid', the EMS targets the site's metering point (受電点), where 'meter_export_ac' and 'meter_import_ac' are measured, and uses the battery to offset generation. At sites with on-site load, this point differs from the inverter's AC output. A value of 0 means no net flow at the meter. For DC-coupled systems in which the battery and PV share an inverter, the EMS honours 'battery' by compensating the AC setpoint for measured PV. Tensor Cloud includes this field on every command and sends 'grid' only to sites whose EMS reports schema_version 2.7.0 or later. If the EMS cannot reach the requested value because of an inverter limit for 'battery' or a state-of-energy limit for 'grid', it clamps the value to what is physically achievable, continues operating, and raises SETPOINT_UNREACHABLE.",
                        "type": "string",
                        "enum": ["battery", "grid"],
                        "default": "battery"
                      }
                    },
                    "additionalProperties": false,
                    "required": ["start_ts", "end_ts", "power_kw"]
                  }
                }
              },
              "additionalProperties": false,
              "required": ["schedule", "priority"]
            }
          },
          "additionalProperties": true,
          "required": ["message_id", "issue_ts", "res_topic", "control", "schema_version"]
        },
        "examples": [
          {
            "name": "Battery Power Command Example",
            "summary": "A schedule that uses both reference points. The first two intervals charge the battery, then place it on standby while any co-located generation exports unchanged. During the third interval, the battery absorbs the generated power to hold the metering point at zero for an export-restriction window.",
            "payload": {
              "schema_version": "2.9.0",
              "message_id": "msg_7c1f0f4a-2f1a-4c3e-9a2b-6d5e4f3a2b1c",
              "issue_ts": "2024-01-04T23:45:00.000+09:00",
              "res_topic": "ack-cmd/si_qcf9gn",
              "control": {
                "priority": 2,
                "schedule": [
                  {
                    "start_ts": "2024-01-05T09:00:00.000+09:00",
                    "end_ts": "2024-01-05T09:30:00.000+09:00",
                    "power_kw": 800.0,
                    "reference_point": "battery"
                  },
                  {
                    "start_ts": "2024-01-05T09:30:00.000+09:00",
                    "end_ts": "2024-01-05T10:00:00.000+09:00",
                    "power_kw": 0,
                    "reference_point": "battery"
                  },
                  {
                    "start_ts": "2024-01-05T10:00:00.000+09:00",
                    "end_ts": "2024-01-05T10:30:00.000+09:00",
                    "power_kw": 0,
                    "reference_point": "grid"
                  }
                ]
              }
            }
          },
          {
            "name": "Battery Power Command Without reference_point",
            "summary": "Both intervals omit reference_point and therefore default to 'battery'. This preserves the meaning of commands emitted before version 2.7.0.",
            "payload": {
              "schema_version": "2.9.0",
              "message_id": "msg_1b2c3d4e-5f60-4718-8293-a4b5c6d7e8f9",
              "issue_ts": "2024-01-04T23:45:00.000+09:00",
              "res_topic": "ack-cmd/si_qcf9gn",
              "control": {
                "priority": 2,
                "schedule": [
                  {
                    "start_ts": "2024-01-05T18:00:00.000+09:00",
                    "end_ts": "2024-01-05T18:30:00.000+09:00",
                    "power_kw": -1500.0
                  },
                  {
                    "start_ts": "2024-01-05T18:30:00.000+09:00",
                    "end_ts": "2024-01-05T19:00:00.000+09:00",
                    "power_kw": 0
                  }
                ]
              }
            }
          }
        ]
      },
      "BatteryFcrCommand": {
        "name": "BatteryFcrCommand",
        "title": "Battery FCR Offer Command",
        "summary": "Command message sent from Tensor Cloud to the EMS for Frequency Containment Reserve (FCR) offers. Sent as a schedule with FCR offer parameters.",
        "correlationId": {
          "description": "Correlation based on message_id for tracing",
          "location": "$message.payload#/message_id"
        },
        "contentType": "application/json",
        "payload": {
          "$schema": "http://json-schema.org/draft-07/schema#",
          "type": "object",
          "properties": {
            "schema_version": {
              "description": "Version of the schema for safe evolution and backward compatibility",
              "type": "string",
              "default": "2.9.0",
              "pattern": "^\\d+\\.\\d+\\.\\d+$",
              "examples": ["2.9.0"]
            },
            "message_id": {
              "description": "ID of the message in prefixed UUID format",
              "type": "string",
              "pattern": "^msg_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "examples": ["msg_a80dd197-47fa-44ca-8966-40d7b4d88277"]
            },
            "issue_ts": {
              "description": "ISO-8601 compliant timestamp of when the command was issued by Tensor Cloud. Used by EMS for prioritization policy execution.",
              "type": "string",
              "format": "date-time",
              "examples": ["2024-01-05T00:00:00.000+09:00"]
            },
            "res_topic": {
              "description": "Response topic where EMS will send command results. Must be within ack-cmd/{siteId} namespace. Messages with res_topic outside this namespace are rejected by the MQTT broker.",
              "type": "string",
              "pattern": "^ack-cmd/si_[a-z0-9]{6}$",
              "examples": ["ack-cmd/si_qcf9gn"]
            },
            "control": {
              "description": "FCR offer control command",
              "type": "object",
              "properties": {
                "priority": {
                  "description": "Priority of the control command. A larger value indicates a higher priority. Ex. Value of 50 has a higher priority than 10.",
                  "type": "number",
                  "minimum": 0,
                  "maximum": 100
                },
                "action": {
                  "description": "Action to perform. 'execute' (default) executes the FCR offer schedule. 'cancel' cancels any previously sent FCR commands within the specified time range, respecting priority rules (only cancels commands with equal or lower priority).",
                  "type": "string",
                  "enum": ["execute", "cancel"],
                  "default": "execute"
                },
                "schedule": {
                  "description": "FCR offer schedule. When action='execute', this contains FCR offers to execute. When action='cancel', this specifies the time ranges to cancel (capacity_kw not required).",
                  "type": "array",
                  "minItems": 1,
                  "items": {
                    "type": "object",
                    "properties": {
                      "start_ts": {
                        "description": "ISO-8601 compliant timestamp indicating the start time of the FCR offer period. Inclusive to the left",
                        "type": "string",
                        "format": "date-time",
                        "examples": ["2024-01-05T00:00:00.000+09:00"]
                      },
                      "end_ts": {
                        "description": "ISO-8601 compliant timestamp indicating the end time of the FCR offer period. Exclusive to the right",
                        "type": "string",
                        "format": "date-time",
                        "examples": ["2024-01-05T00:30:00.000+09:00"]
                      },
                      "capacity_kw": {
                        "description": "FCR offer amount in kilowatts. Represents the capacity offered for frequency containment reserve. Required when action='execute', omitted when action='cancel'.",
                        "type": "number",
                        "minimum": 0,
                        "examples": [10.5, 25.0, 50.0]
                      }
                    },
                    "additionalProperties": false,
                    "required": ["start_ts", "end_ts"]
                  }
                }
              },
              "additionalProperties": false,
              "required": ["schedule", "priority"],
              "if": {
                "properties": {
                  "action": {
                    "const": "cancel"
                  }
                },
                "required": ["action"]
              },
              "then": {
                "properties": {
                  "schedule": {
                    "items": {
                      "not": {
                        "required": ["capacity_kw"]
                      }
                    }
                  }
                }
              },
              "else": {
                "properties": {
                  "schedule": {
                    "items": {
                      "required": ["capacity_kw"]
                    }
                  }
                }
              }
            }
          },
          "additionalProperties": true,
          "required": ["message_id", "issue_ts", "res_topic", "control", "schema_version"]
        },
        "examples": [
          {
            "name": "FCR Offer Command Example",
            "summary": "Execute FCR offers for multiple 30-minute slots",
            "payload": {
              "schema_version": "2.9.0",
              "message_id": "msg_a80dd197-47fa-44ca-8966-40d7b4d88277",
              "issue_ts": "2024-01-04T23:55:00.000+09:00",
              "res_topic": "ack-cmd/si_qcf9gn",
              "control": {
                "priority": 50,
                "action": "execute",
                "schedule": [
                  {
                    "start_ts": "2024-01-05T06:00:00.000+09:00",
                    "end_ts": "2024-01-05T06:30:00.000+09:00",
                    "capacity_kw": 1200.0
                  },
                  {
                    "start_ts": "2024-01-05T06:30:00.000+09:00",
                    "end_ts": "2024-01-05T07:00:00.000+09:00",
                    "capacity_kw": 1500.0
                  }
                ]
              }
            }
          },
          {
            "name": "FCR Cancel Command Example",
            "summary": "Cancel all FCR offers for the entire next day",
            "payload": {
              "schema_version": "2.9.0",
              "message_id": "msg_c4ce10c4-1234-1234-1234-123456789012",
              "issue_ts": "2024-01-05T12:00:00.000+09:00",
              "res_topic": "ack-cmd/si_qcf9gn",
              "control": {
                "priority": 100,
                "action": "cancel",
                "schedule": [
                  {
                    "start_ts": "2024-01-06T00:00:00.000+09:00",
                    "end_ts": "2024-01-07T00:00:00.000+09:00"
                  }
                ]
              }
            }
          },
          {
            "name": "FCR Partial Cancel Example",
            "summary": "Cancel FCR offers for specific 30-minute slots",
            "payload": {
              "schema_version": "2.9.0",
              "message_id": "msg_aabbccdd-3456-7890-abcd-ef1234567890",
              "issue_ts": "2024-01-05T10:30:00.000+09:00",
              "res_topic": "ack-cmd/si_qcf9gn",
              "control": {
                "priority": 75,
                "action": "cancel",
                "schedule": [
                  {
                    "start_ts": "2024-01-05T12:00:00.000+09:00",
                    "end_ts": "2024-01-05T12:30:00.000+09:00"
                  },
                  {
                    "start_ts": "2024-01-05T12:30:00.000+09:00",
                    "end_ts": "2024-01-05T13:00:00.000+09:00"
                  }
                ]
              }
            }
          }
        ]
      },
      "CommandResponse": {
        "name": "CommandResponse",
        "title": "Command Response",
        "summary": "Response message for command execution",
        "correlationId": {
          "description": "Correlation based on message_id for tracing",
          "location": "$message.payload#/message_id"
        },
        "contentType": "application/json",
        "payload": {
          "$schema": "http://json-schema.org/draft-07/schema#",
          "type": "object",
          "properties": {
            "schema_version": {
              "description": "Version of the schema for safe evolution and backward compatibility",
              "type": "string",
              "default": "2.9.0",
              "pattern": "^\\d+\\.\\d+\\.\\d+$",
              "examples": ["2.9.0"]
            },
            "message_id": {
              "description": "Unique message identifier for this response message",
              "type": "string",
              "pattern": "^msg_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "examples": ["msg_a80dd197-47fa-44ca-8966-40d7b4d88277"]
            },
            "status": {
              "description": "Result of receipt-time validation and acceptance, not the execution outcome. The EMS sends this ACK as soon as it has received, parsed, and committed the command. `ok` means the command was well formed and accepted. `error` means validation failed or the command could not be accepted or committed during receipt-time handling; see `errors`. This ACK cannot report failures that occur later during execution. Validation is all-or-nothing: the EMS accepts the complete command or applies none of it. If any item in `control.schedule` is invalid, the EMS returns `error` and continues executing the previously valid schedule.",
              "type": "string",
              "enum": ["ok", "error"]
            },
            "command_id": {
              "description": "message_id of the command message that is being responded to. When the incoming command cannot be parsed (MESSAGE_MALFORMED) and its message_id cannot be recovered, set this to the sentinel string \"unknown\". If the message_id is partially recoverable, echo the recovered value instead. Because of this sentinel case, command_id is intentionally not constrained to the msg_ UUID pattern.",
              "type": "string",
              "examples": ["msg_a80dd197-47fa-44ca-8966-40d7b4d88277", "unknown"]
            },
            "errors": {
              "description": "Errors found in the corresponding command. Include this array with at least one item when status = 'error', and omit it otherwise. The EMS must report every error it finds, including one for each invalid schedule item. Because validation is all-or-nothing, these errors explain why the complete command was rejected; the EMS does not apply the valid items separately.",
              "type": "array",
              "minItems": 1,
              "items": {
                "type": "object",
                "properties": {
                  "code": {
                    "$ref": "#/components/schemas/ErrorCode"
                  },
                  "detail": {
                    "description": "Detailed error message providing context",
                    "type": "string",
                    "examples": [
                      "Power value exceeds battery maximum capacity",
                      "Connection to the battery lost"
                    ]
                  },
                  "field_path": {
                    "description": "Path to the field that caused the error. Optional because not all errors are a direct result of the command schedule contents",
                    "type": "string",
                    "examples": ["control.schedule[0].power_kw"]
                  }
                },
                "required": ["code", "detail"],
                "additionalProperties": false
              }
            }
          },
          "additionalProperties": true,
          "required": ["message_id", "command_id", "status", "schema_version"],
          "if": {
            "properties": {
              "status": {
                "const": "error"
              }
            }
          },
          "then": {
            "required": ["errors"]
          },
          "else": {
            "not": {
              "required": ["errors"]
            }
          }
        },
        "examples": [
          {
            "name": "Command Response Success Example",
            "summary": "The EMS accepts the command without error",
            "payload": {
              "schema_version": "2.9.0",
              "message_id": "msg_a80dd191-47fa-44ca-3243-40d7b4d88277",
              "status": "ok",
              "command_id": "msg_a80dd197-47fa-44ca-8966-40d7b4d88277"
            }
          },
          {
            "name": "Command Response Error Example",
            "summary": "Tensor Cloud has sent a discharge power value that is larger than the maximum battery power capacity",
            "payload": {
              "schema_version": "2.9.0",
              "message_id": "msg_a80dd191-47fa-44ca-3243-40d7b4d88277",
              "status": "error",
              "command_id": "msg_a80dd197-47fa-44ca-8966-40d7b4d88277",
              "errors": [
                {
                  "code": "FIELD_OUT_OF_RANGE",
                  "detail": "Power value exceeds battery maximum capacity",
                  "field_path": "control.schedule[0].power_kw"
                }
              ]
            }
          },
          {
            "name": "Command Response Multiple Errors Example",
            "summary": "EMS rejects a command due to several validation failures in the schedule. Slots not named here were valid, but validation is all-or-nothing, so no slot from this command is applied and the previously valid schedule keeps running",
            "payload": {
              "schema_version": "2.9.0",
              "message_id": "msg_44444444-5555-6666-7777-888888888888",
              "status": "error",
              "command_id": "msg_a80dd197-47fa-44ca-8966-40d7b4d88277",
              "errors": [
                {
                  "code": "FIELD_OUT_OF_RANGE",
                  "detail": "Power value exceeds battery maximum capacity",
                  "field_path": "control.schedule[0].power_kw"
                },
                {
                  "code": "TIME_WINDOW_INVALID",
                  "detail": "End timestamp must be after start timestamp",
                  "field_path": "control.schedule[1].end_ts"
                },
                {
                  "code": "MISSING_FIELD",
                  "detail": "Priority field is required",
                  "field_path": "control.priority"
                }
              ]
            }
          },
          {
            "name": "Command Response Malformed Example",
            "summary": "The command could not be parsed as JSON, so its message_id cannot be recovered; command_id falls back to the \"unknown\" sentinel",
            "payload": {
              "schema_version": "2.9.0",
              "message_id": "msg_55555555-6666-7777-8888-999999999999",
              "status": "error",
              "command_id": "unknown",
              "errors": [
                {
                  "code": "MESSAGE_MALFORMED",
                  "detail": "Command payload is not valid JSON"
                }
              ]
            }
          }
        ]
      },
      "AlertEvent": {
        "name": "AlertEvent",
        "title": "Alert Event",
        "summary": "State-change event for an alert.",
        "correlationId": {
          "description": "Correlation based on message_id for tracing",
          "location": "$message.payload#/message_id"
        },
        "contentType": "application/json",
        "payload": {
          "$schema": "http://json-schema.org/draft-07/schema#",
          "type": "object",
          "properties": {
            "schema_version": {
              "description": "Version of the schema for safe evolution and backward compatibility",
              "type": "string",
              "default": "2.9.0",
              "pattern": "^\\d+\\.\\d+\\.\\d+$",
              "examples": ["2.9.0"]
            },
            "message_id": {
              "description": "ID of the message in prefixed UUID format",
              "type": "string",
              "pattern": "^msg_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "examples": ["msg_a80dd197-47fa-44ca-8966-40d7b4d88277"]
            },
            "measurement_ts": {
              "description": "ISO-8601 timestamp of when the change in alert state was measured by the EMS",
              "type": "string",
              "format": "date-time",
              "examples": ["2024-01-04T03:26:10.000+09:00"]
            },
            "measurement_value": {
              "type": "object",
              "properties": {
                "code": {
                  "$ref": "#/components/schemas/AlertCode",
                  "description": "Identifier of the alert type."
                },
                "status": {
                  "description": "Whether the alert is ongoing (active) or has been resolved (cleared).",
                  "type": "string",
                  "enum": ["active", "cleared"]
                }
              },
              "required": ["code", "status"],
              "additionalProperties": false
            }
          },
          "required": ["message_id", "measurement_ts", "measurement_value", "schema_version"],
          "additionalProperties": true
        }
      },
      "AlertState": {
        "name": "AlertState",
        "title": "Alert Snapshot",
        "summary": "Snapshot of currently active alerts.",
        "correlationId": {
          "description": "Correlation based on message_id for tracing",
          "location": "$message.payload#/message_id"
        },
        "contentType": "application/json",
        "payload": {
          "$schema": "http://json-schema.org/draft-07/schema#",
          "type": "object",
          "properties": {
            "schema_version": {
              "description": "Version of the schema for safe evolution and backward compatibility",
              "type": "string",
              "default": "2.9.0",
              "pattern": "^\\d+\\.\\d+\\.\\d+$",
              "examples": ["2.9.0"]
            },
            "message_id": {
              "description": "ID of the message in prefixed UUID format",
              "type": "string",
              "pattern": "^msg_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "examples": ["msg_99999999-8888-7777-6666-555555555555"]
            },
            "measurement_ts": {
              "description": "ISO-8601 compliant timestamp of when the snapshot was taken by the EMS",
              "type": "string",
              "format": "date-time",
              "examples": ["2025-09-10T11:20:00+09:00"]
            },
            "measurement_value": {
              "description": "List of alerts and their current states. Publish the snapshot on schedule even when no alerts are active; in that case, send an empty array. Do not suppress an empty snapshot, because Tensor Cloud must be able to distinguish a healthy site from a silent one.",
              "type": "array",
              "items": {
                "type": "object",
                "properties": {
                  "code": {
                    "$ref": "#/components/schemas/AlertCode",
                    "description": "Identifier of the alert type."
                  },
                  "first_seen_ts": {
                    "type": "string",
                    "format": "date-time",
                    "description": "ISO-8601 compliant timestamp when this alert was first observed as active."
                  },
                  "last_seen_ts": {
                    "type": "string",
                    "format": "date-time",
                    "description": "ISO-8601 compliant timestamp when this alert was last observed as active."
                  }
                },
                "required": ["code", "first_seen_ts", "last_seen_ts"],
                "additionalProperties": false
              },
              "minItems": 0
            }
          },
          "required": ["message_id", "measurement_ts", "measurement_value", "schema_version"],
          "additionalProperties": true
        },
        "examples": [
          {
            "name": "FirstActiveAlert",
            "summary": "EMS first raises a low SOC alert",
            "payload": {
              "schema_version": "2.9.0",
              "message_id": "msg_11111111-2222-3333-4444-555555555555",
              "measurement_ts": "2025-09-15T10:00:00+09:00",
              "measurement_value": [
                {
                  "code": "BATT_SOC_LOW",
                  "first_seen_ts": "2025-09-15T10:00:00+09:00",
                  "last_seen_ts": "2025-09-15T10:00:00+09:00"
                }
              ]
            }
          },
          {
            "name": "OngoingActiveAlert",
            "summary": "EMS snapshot with the alert still active",
            "payload": {
              "schema_version": "2.9.0",
              "message_id": "msg_22222222-3333-4444-5555-666666666666",
              "measurement_ts": "2025-09-15T10:05:00+09:00",
              "measurement_value": [
                {
                  "code": "BATT_SOC_LOW",
                  "first_seen_ts": "2025-09-15T10:00:00+09:00",
                  "last_seen_ts": "2025-09-15T10:05:00+09:00"
                }
              ]
            }
          },
          {
            "name": "MultipleAlerts",
            "summary": "Snapshot with two simultaneous active alerts",
            "payload": {
              "schema_version": "2.9.0",
              "message_id": "msg_44444444-5555-6666-7777-888888888888",
              "measurement_ts": "2025-09-15T10:15:00+09:00",
              "measurement_value": [
                {
                  "code": "BATT_SOC_LOW",
                  "first_seen_ts": "2025-09-15T10:00:00+09:00",
                  "last_seen_ts": "2025-09-15T10:15:00+09:00"
                },
                {
                  "code": "PV_COMM_FAIL",
                  "first_seen_ts": "2025-09-15T10:12:00+09:00",
                  "last_seen_ts": "2025-09-15T10:15:00+09:00"
                }
              ]
            }
          }
        ]
      },
      "TelemetryFeedback": {
        "name": "TelemetryFeedback",
        "title": "Telemetry Feedback",
        "summary": "Developer feedback for telemetry messages. Testing environment only; ignored in production.",
        "correlationId": {
          "description": "Correlation based on message_id for tracing",
          "location": "$message.payload#/message_id"
        },
        "contentType": "application/json",
        "payload": {
          "$schema": "http://json-schema.org/draft-07/schema#",
          "type": "object",
          "properties": {
            "schema_version": {
              "description": "Version of the schema for safe evolution and backward compatibility",
              "type": "string",
              "default": "2.9.0",
              "pattern": "^\\d+\\.\\d+\\.\\d+$",
              "examples": ["2.9.0"]
            },
            "message_id": {
              "description": "Unique message identifier for this response message",
              "type": "string",
              "pattern": "^msg_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "examples": ["msg_a80dd197-47fa-44ca-8966-40d7b4d88277"]
            },
            "correlation_id": {
              "description": "`message_id` of the telemetry message that triggered this feedback. When the incoming telemetry cannot be parsed (MESSAGE_MALFORMED) and its message_id cannot be recovered, set this to the sentinel string \"unknown\". If the message_id is partially recoverable, echo the recovered value instead. Because of this sentinel case, correlation_id is intentionally not constrained to the msg_ UUID pattern.",
              "type": "string",
              "examples": ["msg_a80dd197-47fa-44ca-8966-40d7b4d88277", "unknown"]
            },
            "status": {
              "description": "Validation status",
              "type": "string",
              "enum": ["ok", "error"]
            },
            "code": {
              "description": "Error code if status is error",
              "$ref": "#/components/schemas/ErrorCode"
            },
            "field_path": {
              "description": "Path to the field that caused the error, if applicable",
              "type": "string",
              "examples": ["measurement_value.value"]
            },
            "topic": {
              "description": "Topic on which the rejected telemetry message was published. Include this optional field only when status is error. When correlation_id is the \"unknown\" sentinel, including the topic is strongly recommended because it is the only information the EMS can use to identify the rejected message.",
              "type": "string",
              "examples": ["dt/site-123/gw-1/battery/state"]
            },
            "detail": {
              "description": "Detailed error message",
              "type": "string",
              "examples": ["soe_kwh must be positive"]
            }
          },
          "required": ["message_id", "correlation_id", "status", "schema_version"],
          "additionalProperties": true,
          "if": {
            "properties": {
              "status": {
                "const": "error"
              }
            }
          },
          "then": {
            "required": ["code", "detail"]
          },
          "else": {
            "not": {
              "anyOf": [
                {
                  "required": ["code"]
                },
                {
                  "required": ["detail"]
                },
                {
                  "required": ["topic"]
                }
              ]
            }
          }
        },
        "examples": [
          {
            "name": "Telemetry message passed validation",
            "payload": {
              "schema_version": "2.9.0",
              "message_id": "msg_11111111-2222-3333-4444-555555555555",
              "correlation_id": "msg_a80dd197-47fa-44ca-8966-40d7b4d88277",
              "status": "ok"
            }
          },
          {
            "name": "Telemetry message did not pass validation",
            "payload": {
              "schema_version": "2.9.0",
              "message_id": "msg_22222222-3333-4444-5555-666666666666",
              "correlation_id": "msg_a80dd197-47fa-44ca-8966-40d7b4d88277",
              "status": "error",
              "code": "FIELD_OUT_OF_RANGE",
              "field_path": "measurement_value.value",
              "topic": "dt/site-123/gw-1/battery/state",
              "detail": "soe_kwh must be positive"
            }
          },
          {
            "name": "Malformed telemetry message",
            "summary": "The telemetry could not be parsed as JSON, so its message_id cannot be recovered; correlation_id falls back to the \"unknown\" sentinel and topic identifies the source channel",
            "payload": {
              "schema_version": "2.9.0",
              "message_id": "msg_33333333-4444-5555-6666-777777777777",
              "correlation_id": "unknown",
              "status": "error",
              "code": "MESSAGE_MALFORMED",
              "topic": "dt/site-123/gw-1/battery/state",
              "detail": "Telemetry payload is not valid JSON"
            }
          }
        ]
      }
    },
    "schemas": {
      "AlertCode": {
        "title": "Alert Code",
        "description": "Standardized alert codes. See 'Alert codes reference' in the integration guide for thresholds and mapping guidance. One site fault may raise multiple codes. On a DC-linked system, report a fault in hardware shared by the PV array and battery, such as a hybrid inverter, shared power conditioning subsystem, or common control or aggregation unit, as both `PV_OTHER` and `BATT_OTHER`. If one collector connects the EMS to both the battery and PV and the EMS cannot read communication status separately for each BMS or PVPCS, a communication loss with that collector may raise both `BATT_COMM_FAIL` and `PV_COMM_FAIL`.",
        "type": "string",
        "enum": [
          "BATT_SOC_LOW",
          "BATT_SOH_DEGRADED",
          "BATT_OVERTEMP",
          "BATT_COMM_FAIL",
          "BATT_OTHER",
          "PV_COMM_FAIL",
          "PV_OTHER",
          "CURT_COMM_FAIL",
          "GRID_OTHER",
          "SETPOINT_UNREACHABLE",
          "UNKNOWN_FAULT"
        ]
      },
      "ErrorCode": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "title": "Error Code",
        "description": "Standardized error codes. See 'Error codes' in the protocol specification for detailed semantics. `INTERNAL_ERROR` also covers a well-formed command that could not be accepted or committed during receipt-time handling before the ACK was sent, such as an internal failure while committing a schedule change.",
        "type": "string",
        "enum": [
          "MESSAGE_MALFORMED",
          "MISSING_FIELD",
          "TYPE_MISMATCH",
          "FIELD_OUT_OF_RANGE",
          "TIME_WINDOW_INVALID",
          "DUPLICATE",
          "EXPIRED",
          "RESOURCE_UNAVAILABLE",
          "INTERNAL_ERROR"
        ]
      }
    },
    "securitySchemes": {
      "awsIotCore": {
        "type": "X509",
        "description": "AWS IoT Core X.509 certificate-based authentication. Certificates are issued by your Tensor Energy technical contact."
      }
    }
  }
}
