{
  "asyncapi": "3.1.0",
  "info": {
    "title": "Tensor Cloud battery optimization API",
    "version": "2.8.0",
    "description": "Communication protocol specification for the Tensor Cloud battery optimization service. This specification helps EMS devices communicate with Tensor Cloud through MQTT messaging. Timestamp convention: on windowed measurements - windowed energy telemetry, and power or grid frequency telemetry sent with aggregation 'average' - 'measurement_ts' always carries the start of the period the value covers, never its end or midpoint. On point-in-time telemetry it is simply the instant the value was measured. Curtailment schedules are the exception: there 'measurement_ts' is the schedule's TSO-issued creation time, or the time the EMS received it from the TSO.",
    "termsOfService": "https://docs.tensorenergy.jp/en/legal/terms-of-use/",
    "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": "Lifetime cumulative energy (kWh) should be published to this topic. Tensor Cloud requires either lifetime or windowed readings to be sent, ideally both.",
      "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": "Aggregated energy (kWh per window) should be published to this topic. Tensor Cloud requires either windowed or lifetime readings to be sent, ideally both.",
      "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": "Instantaneous power (kW) telemetry readings should be published to topics matching this pattern. While instantaneous power readings are not strictly required for the Tensor Cloud battery optimization to work, they can increase accuracy if sent at high frequency (10 minutes or less). When a resource offers FCR as an offline-monitored resource, this topic also carries the 1-second-resolution supplied power used for FCR Assessment II: publish meter_export_ac and meter_import_ac (grid-connection-point registration) or battery_discharge_ac and battery_charge_ac (equipment-point registration) at 1 Hz for every FCR-awarded slot. See the integration guide's 'FCR Assessment II reporting' section.",
      "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": "Telemetry about the momentary operational state of a device should be published to this topic. Tensor Cloud requires battery SoE and remaining energy for battery optimization.",
      "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": "Curtailment schedule telemetry data is published to this topic. Every topology must publish it, 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": "On-site solar irradiation sensor readings should be published 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": "PV-array DC voltage and current for DC-coupled (DC-link) PV plus battery systems should be published to this topic. Send voltage and current as a matched pair sampled at the same instant. 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": "Grid frequency (Hz) telemetry readings should be published to this topic. When a resource has been awarded FCR, the EMS MUST publish grid frequency at 1 Hz (one reading per second) for the full duration of every FCR-awarded slot, as the mean over each 1-second window. Frequency must be measured at the grid connection point from the live AC voltage waveform and reported to at least 0.0001 Hz resolution; do not publish a nominal, scheduled, or otherwise derived value. These readings are time-aligned with the 1 Hz supplied-power telemetry used for FCR Assessment II so the two can be compared directly. See the integration guide's 'FCR Assessment II reporting' section.",
      "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. Carries many readings of a single metric in one message instead of one message per reading, which is intended for high-rate 1 Hz publishing and for recovering a backlog after a connection loss. Each reading keeps its own message_id and measurement_ts; unit, aggregation, aggregation_window and pre_qualification are stated once for the whole batch. A batch is accepted in full or rejected in full. Batching is enabled per site by Tensor Cloud and is backfill-only by default: sites whose live telemetry must arrive every second, such as low-voltage batteries in a VPP configuration, publish live readings as single messages and batch only backfill. See the integration guide's 'Batched telemetry' section.",
      "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, following the same rules as the batched power topic: many readings in one message, each keeping its own message_id and measurement_ts, with unit, aggregation, aggregation_window and pre_qualification stated once for the whole batch. A batch is accepted in full or rejected in full. Batching is enabled per site by Tensor Cloud and is backfill-only by default. See the integration guide's 'Batched telemetry' section.",
      "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 command schedules. When the battery is operated in FCR mode, this command also determines the baseline output for FCR participation, and the baseline inherits each interval's reference_point. Every interval states where its setpoint is measured through reference_point; it is never inferred from the site's topology.",
      "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": "Tensor Cloud will respond to every telemetry message from the EMS with an explicit `error`/`ok` acknowledgement in this channel. Used by EMS developers during development. Testing environment only; ignored in production. Response topics are constrained 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\nNote that not all metrics are required. Refer to the integration guide to understand valid combinations for your use case.",
        "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). Note that not all metrics are required for every use case. Refer to 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). This should be an increasing counter value. Tensor Cloud can handle 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. At a minimum, battery state of energy (SoE), and energy remaining (remaining kWh of battery capacity at nominal temperature, including degradation effects) are required.\n\n- battery_soe: Current energy stored in the battery at this moment (kWh equivalent of State of Charge). Updates frequently. Valid range: 0 ≤ battery_soe ≤ battery_energy_remaining.\n- battery_energy_remaining: Total usable capacity of the battery at nominal temperature (~25°C), accounting for degradation and faulty cells. Represents maximum energy the battery can hold. Updates 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.8.0",
              "pattern": "^\\d+\\.\\d+\\.\\d+$",
              "examples": ["2.8.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. This should be a counter that can only increase over time, similar to an electricity meter. Tensor Cloud can handle 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.8.0",
              "pattern": "^\\d+\\.\\d+\\.\\d+$",
              "examples": ["2.8.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. This is a windowed measurement, so it must carry the start of the window and equal measurement_value.start_ts - never the window end or midpoint.",
              "type": "string",
              "format": "date-time",
              "examples": ["2024-01-04T03:00:00.000+09:00"]
            },
            "measurement_value": {
              "description": "Energy measurement for the window. Timestamps should be inclusive on the left, and exclusive on the right. That means, that an *end_ts* timestamp of 10:30 would include up to 10:29:59.999... but not 10:30 itself.",
              "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.8.0",
              "pattern": "^\\d+\\.\\d+\\.\\d+$",
              "examples": ["2.8.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. For 'instant' readings this is the moment the sample was taken. For windowed ('average') readings it must carry the start of aggregation_window - never 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.8.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.8.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.8.0",
              "pattern": "^\\d+\\.\\d+\\.\\d+$",
              "examples": ["2.8.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's creation time as issued by the TSO when the TSO data carries it; otherwise use the time the EMS received the schedule from the TSO. This is not a windowed measurement - the periods the schedule covers are carried 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`",
                    "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`",
                    "type": "string",
                    "format": "date-time",
                    "examples": ["2024-01-04T14:30:00.000+09:00"]
                  },
                  "limit_percent": {
                    "description": "Percentage of maximum site output permitted by TSO. 0 equals 100% of energy being curtailed with no site output permitted. Value should be within the 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.8.0",
              "pattern": "^\\d+\\.\\d+\\.\\d+$",
              "examples": ["2.8.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.8.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.8.0",
              "pattern": "^\\d+\\.\\d+\\.\\d+$",
              "examples": ["2.8.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.8.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.8.0",
              "pattern": "^\\d+\\.\\d+\\.\\d+$",
              "examples": ["2.8.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. For 'instant' readings this is the moment the sample was taken. For windowed ('average') readings it must carry the start of aggregation_window - never 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.0001 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.8.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.8.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.8.0",
              "pattern": "^\\d+\\.\\d+\\.\\d+$",
              "examples": ["2.8.0"]
            },
            "message_id": {
              "description": "ID of this batch message in prefixed UUID format. It identifies the publish attempt only and is NOT a deduplication key: the same measurements may be re-grouped into a differently-sized batch on resend, and each measurement carries its own message_id which is the key Tensor Cloud deduplicates on.",
              "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 when the EMS is in FCR pre-qualification (事前審査) test mode, a mode activated by the EMS operator, not by Tensor Cloud. Applies to every entry in 'measurements', so a batch must not straddle the moment the operator enters or leaves the mode - split it there. Omit (or set false) during normal operation.",
              "type": "boolean",
              "default": false
            },
            "measurements": {
              "description": "The readings carried by this batch, in strictly ascending 'measurement_ts' order with no repeated timestamp and no repeated message_id. A second with no reliable reading is simply absent - never publish 0 or null in its place. The batch is accepted in full or rejected in full.",
              "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 of when this reading was measured by the EMS. For 'instant' readings this is the moment the sample was taken. For 'average' readings it must carry the start of aggregation_window - never 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.8.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.8.0",
              "pattern": "^\\d+\\.\\d+\\.\\d+$",
              "examples": ["2.8.0"]
            },
            "message_id": {
              "description": "ID of this batch message in prefixed UUID format. It identifies the publish attempt only and is NOT a deduplication key: the same measurements may be re-grouped into a differently-sized batch on resend, and each measurement carries its own message_id which is the key Tensor Cloud deduplicates on.",
              "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 when the EMS is in FCR pre-qualification (事前審査) test mode, a mode activated by the EMS operator, not by Tensor Cloud. Applies to every entry in 'measurements', so a batch must not straddle the moment the operator enters or leaves the mode - split it there. Omit (or set false) during normal operation.",
              "type": "boolean",
              "default": false
            },
            "measurements": {
              "description": "The readings carried by this batch, in strictly ascending 'measurement_ts' order with no repeated timestamp and no repeated message_id. A second with no reliable reading is simply absent - never publish 0 or null in its place. The batch is accepted in full or rejected in full.",
              "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 of when this reading was measured by the EMS. For 'instant' readings this is the moment the sample was taken. For 'average' readings it must carry the start of aggregation_window - never 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.0001 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.8.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.8.0",
              "pattern": "^\\d+\\.\\d+\\.\\d+$",
              "examples": ["2.8.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": "Command message sent from Tensor Cloud to the EMS to control battery charge/discharge behavior. Sent as a schedule with 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.8.0",
              "pattern": "^\\d+\\.\\d+\\.\\d+$",
              "examples": ["2.8.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": "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
                },
                "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": "Amount of power in kilowatt to charge/discharge. Positive values indicate charging and negative number indicate discharging. Value of 0 indicates to stand by. The point this value refers to is carried by 'reference_point' on the same interval, and is never inferred from the site's topology.",
                        "type": "number",
                        "examples": [10.123, -5.5, 0]
                      },
                      "reference_point": {
                        "description": "Where 'power_kw' is measured for this interval. 'battery' (the default when the field is absent) means the value refers to the battery itself: the EMS drives the battery to it and lets any co-located generation flow through untouched, so 0 means the battery stands by. 'grid' means the value refers to the site's metering point (受電点) - the same point 'meter_export_ac' and 'meter_import_ac' are measured at, which on a site with on-site load is not the inverter's AC output - and the battery makes up the difference between that target and whatever generation is doing, so 0 means no net flow at the meter rather than a battery stand-down. On DC-coupled systems, where the battery and PV share an inverter, the EMS honours 'battery' by compensating its AC setpoint against measured PV. Tensor Cloud states this field on every command rather than relying on the default, and only sends 'grid' to sites whose EMS reports schema_version 2.7.0 or later. If the EMS cannot reach the requested value - an inverter limit on 'battery', a state-of-energy limit on 'grid' - it clamps to what is physically achievable, keeps running, 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 mixing both reference points. The first two intervals act on the battery - charge, then stand by while any co-located generation exports untouched. The third holds the metering point at zero for an export-restriction window, which the battery meets by absorbing whatever is being generated",
            "payload": {
              "schema_version": "2.8.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": "reference_point is omitted, so both intervals default to 'battery'. This is the shape emitted before 2.7.0 and it keeps exactly the meaning it always had",
            "payload": {
              "schema_version": "2.8.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.8.0",
              "pattern": "^\\d+\\.\\d+\\.\\d+$",
              "examples": ["2.8.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.8.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.8.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.8.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.8.0",
              "pattern": "^\\d+\\.\\d+\\.\\d+$",
              "examples": ["2.8.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 validating and accepting the command at receipt time (not the execution outcome). The ACK is sent immediately after the command is received, parsed, and committed, not at execution time. `ok` = the command was well-formed and accepted; `error` = it failed validation, or could not be accepted/committed during receipt-time handling (see `errors`). A failure that surfaces only later at execution time cannot be reported through this ACK. Validation is all-or-nothing: there is no partial status. A command is either accepted in full, which yields `ok`, or rejected without any of it being applied, which yields `error` - it is never applied in part. If any item in `control.schedule` is invalid, the EMS applies none of the items in that command and keeps 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": "Array of errors (must be present and contain at least one item when status = 'error'; omitted otherwise). EMS is required to send one item in the array for each error found in the correlating message, including one per invalid schedule item rather than stopping at the first. Because validation is all-or-nothing, this array reports why the whole command was rejected; it never describes a subset of items that were skipped while the rest were applied.",
              "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.8.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.8.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.8.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.8.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.8.0",
              "pattern": "^\\d+\\.\\d+\\.\\d+$",
              "examples": ["2.8.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.8.0",
              "pattern": "^\\d+\\.\\d+\\.\\d+$",
              "examples": ["2.8.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. An empty array is valid and expected: the snapshot is published on its schedule whether or not any alert is firing, and an empty array is the positive statement that nothing is active. Suppressing the publish when there are no active alerts is not permitted, because Tensor Cloud cannot then 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.8.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.8.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.8.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.8.0",
              "pattern": "^\\d+\\.\\d+\\.\\d+$",
              "examples": ["2.8.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 the offending telemetry message was published on. Optional, and only present when status is error. Strongly recommended whenever correlation_id is the \"unknown\" sentinel, because it is then the only information the EMS has to identify which message was rejected.",
              "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.8.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.8.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.8.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 the integration guide's 'Alert codes reference' section for thresholds and mapping guidance. A single site fault may legitimately raise more than one code: on a DC-linked system a fault in hardware shared by the PV array and the battery (a hybrid inverter, a shared power conditioning subsystem, a common control or aggregation unit) is reported as both `PV_OTHER` and `BATT_OTHER`, and where the EMS reaches the battery and the PV through one collector device and cannot see per-BMS or per-PVPCS communication status, a loss of communication with that collector may be reported as both `BATT_COMM_FAIL` and `PV_COMM_FAIL` at the same time.",
        "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 protocol specification section 'Error codes' for detailed semantics. Note that `INTERNAL_ERROR` also covers a well-formed command that failed during receipt-time handling and could not be accepted/committed before the ACK is sent (e.g. an internal failure committing the requested 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."
      }
    }
  }
}
