{
  "asyncapi": "3.1.0",
  "info": {
    "title": "Tensor Cloud battery optimization API",
    "version": "2.6.0",
    "description": "Communication protocol specification for the Tensor Cloud battery optimization service. This specification helps EMS devices communicate with Tensor Cloud through MQTT messaging.",
    "termsOfService": "https://docs.tensorenergy.jp/en/legal/terms-of-use/",
    "externalDocs": {
      "description": "Tensor Cloud battery optimization documentation",
      "url": "https://docs.tensorenergy.jp/en/api/battery-optimization"
    },
    "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 should be published to this topic.",
      "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"
        }
      }
    },
    "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.",
      "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"
        }
      ]
    },
    "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.6.0",
              "pattern": "^\\d+\\.\\d+\\.\\d+$",
              "examples": ["2.6.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.6.0",
              "pattern": "^\\d+\\.\\d+\\.\\d+$",
              "examples": ["2.6.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": "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.6.0",
              "pattern": "^\\d+\\.\\d+\\.\\d+$",
              "examples": ["2.6.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"]
            },
            "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.6.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.6.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. This message is optional, but we recommend sending it for improved battery optimization, especially for stand-alone battery systems.",
        "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.6.0",
              "pattern": "^\\d+\\.\\d+\\.\\d+$",
              "examples": ["2.6.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": "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.6.0",
              "pattern": "^\\d+\\.\\d+\\.\\d+$",
              "examples": ["2.6.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.6.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.6.0",
              "pattern": "^\\d+\\.\\d+\\.\\d+$",
              "examples": ["2.6.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.6.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.6.0",
              "pattern": "^\\d+\\.\\d+\\.\\d+$",
              "examples": ["2.6.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 'average' readings, this is the start of the aggregation window.",
              "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.6.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.6.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"
              }
            }
          }
        ]
      },
      "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.6.0",
              "pattern": "^\\d+\\.\\d+\\.\\d+$",
              "examples": ["2.6.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.6.0",
              "pattern": "^\\d+\\.\\d+\\.\\d+$",
              "examples": ["2.6.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.",
                        "type": "number",
                        "examples": [10.123, -5.5, 0]
                      }
                    },
                    "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"]
        }
      },
      "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.6.0",
              "pattern": "^\\d+\\.\\d+\\.\\d+$",
              "examples": ["2.6.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.6.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.6.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.6.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.6.0",
              "pattern": "^\\d+\\.\\d+\\.\\d+$",
              "examples": ["2.6.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.",
              "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.",
              "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.6.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.6.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",
            "payload": {
              "schema_version": "2.6.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.6.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.6.0",
              "pattern": "^\\d+\\.\\d+\\.\\d+$",
              "examples": ["2.6.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.6.0",
              "pattern": "^\\d+\\.\\d+\\.\\d+$",
              "examples": ["2.6.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.",
              "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.6.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.6.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.6.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.6.0",
              "pattern": "^\\d+\\.\\d+\\.\\d+$",
              "examples": ["2.6.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.6.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.6.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.6.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": {
        "type": "string",
        "enum": [
          "BATT_SOC_LOW",
          "BATT_SOH_DEGRADED",
          "BATT_OVERTEMP",
          "BATT_COMM_FAIL",
          "BATT_OTHER",
          "PV_COMM_FAIL",
          "PV_OTHER",
          "CURT_COMM_FAIL",
          "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."
      }
    }
  }
}
