{
  "asyncapi": "3.0.0",
  "info": {
    "title": "Tensor Cloud battery optimization API",
    "version": "2.2.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 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).",
      "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"
        }
      }
    },
    "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 Bid Command Schedule",
      "description": "Topic for battery Frequency Containment Reserve bid 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"
        }
      ]
    },
    "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 bid commands",
      "description": "EMS gateway receives battery FCR (Frequency Containment Reserve) bid 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.2.0",
              "pattern": "^\\d+\\.\\d+\\.\\d+$",
              "examples": ["2.2.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.2.0",
              "pattern": "^\\d+\\.\\d+\\.\\d+$",
              "examples": ["2.2.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.2.0",
              "pattern": "^\\d+\\.\\d+\\.\\d+$",
              "examples": ["2.2.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": "Instantaneous power measurement",
              "type": "object",
              "properties": {
                "value": {
                  "description": "Instantaneous power value",
                  "type": "number",
                  "minimum": 0
                },
                "unit": {
                  "description": "Unit of measurement",
                  "type": "string",
                  "enum": ["kW"]
                }
              },
              "required": ["value", "unit"],
              "additionalProperties": false
            }
          },
          "additionalProperties": true,
          "required": ["message_id", "measurement_ts", "measurement_value", "schema_version"]
        }
      },
      "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.2.0",
              "pattern": "^\\d+\\.\\d+\\.\\d+$",
              "examples": ["2.2.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.2.0",
              "pattern": "^\\d+\\.\\d+\\.\\d+$",
              "examples": ["2.2.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.2.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"
              }
            }
          }
        ]
      },
      "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.2.0",
              "pattern": "^\\d+\\.\\d+\\.\\d+$",
              "examples": ["2.2.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.2.0",
              "pattern": "^\\d+\\.\\d+\\.\\d+$",
              "examples": ["2.2.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 Bid Command",
        "summary": "Command message sent from Tensor Cloud to the EMS for Frequency Containment Reserve (FCR) bidding. Sent as a schedule with FCR bid 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.2.0",
              "pattern": "^\\d+\\.\\d+\\.\\d+$",
              "examples": ["2.2.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 bid 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 bid 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 bid schedule. When action='execute', this contains FCR bids 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 bid 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 bid period. Exclusive to the right",
                        "type": "string",
                        "format": "date-time",
                        "examples": ["2024-01-05T00:30:00.000+09:00"]
                      },
                      "capacity_kw": {
                        "description": "FCR bid 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 Bid Command Example",
            "summary": "Execute FCR bids for multiple 3-hour periods",
            "payload": {
              "schema_version": "2.2.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-05T09:00:00.000+09:00",
                    "capacity_kw": 1200.0
                  },
                  {
                    "start_ts": "2024-01-05T09:00:00.000+09:00",
                    "end_ts": "2024-01-05T12:00:00.000+09:00",
                    "capacity_kw": 1500.0
                  }
                ]
              }
            }
          },
          {
            "name": "FCR Cancel Command Example",
            "summary": "Cancel all FCR bids for the entire next day",
            "payload": {
              "schema_version": "2.2.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 bids for specific 3-hour periods",
            "payload": {
              "schema_version": "2.2.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-05T15:00:00.000+09:00"
                  },
                  {
                    "start_ts": "2024-01-05T15:00:00.000+09:00",
                    "end_ts": "2024-01-05T18: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.2.0",
              "pattern": "^\\d+\\.\\d+\\.\\d+$",
              "examples": ["2.2.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": "Status of command execution",
              "type": "string",
              "enum": ["ok", "error"]
            },
            "command_id": {
              "description": "message_id of the command message that is being responded to",
              "type": "string",
              "examples": ["msg_a80dd197-47fa-44ca-8966-40d7b4d88277"]
            },
            "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.2.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.2.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.2.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"
                }
              ]
            }
          }
        ]
      },
      "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.2.0",
              "pattern": "^\\d+\\.\\d+\\.\\d+$",
              "examples": ["2.2.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.2.0",
              "pattern": "^\\d+\\.\\d+\\.\\d+$",
              "examples": ["2.2.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.2.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.2.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.2.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.2.0",
              "pattern": "^\\d+\\.\\d+\\.\\d+$",
              "examples": ["2.2.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",
              "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": "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"]
            },
            "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"]
                }
              ]
            }
          }
        },
        "examples": [
          {
            "name": "Telemetry message passed validation",
            "payload": {
              "schema_version": "2.2.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.2.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",
              "detail": "soe_kwh must be positive"
            }
          }
        ]
      }
    },
    "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.",
        "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."
      }
    }
  }
}
