{
  "openapi": "3.0.3",
  "info": {
    "title": "EOS IoT Platform API",
    "description": "# EOS IoT Platform API - User Guide\n\n**Note:** This is a new product (version 1.0.0). We welcome any feedback to help us improve the API. Please don't hesitate to share your thoughts, suggestions, or report any issues you encounter.\n\nREST API to access IoT device data from the EOS platform.\nThis documentation guides you through using the API to integrate device data into your applications.\n\n## Authentication\n\nAll requests must include the `x-api-key` header with your API key provided during registration.\n\n**Example:**\n```bash\ncurl -H \"x-api-key: YOUR_API_KEY\" https://p2uyaymt8c.execute-api.eu-south-1.amazonaws.com/v1/integration/devices\n```\n\n**Security:**\n- ⚠️ Never share your API key\n- ⚠️ Do not commit it to source code\n- ✅ Use environment variables to store it\n\n## Multiple Measurements\n\nSome devices may have **multiple sensors of the same type**. For example, a device may have:\n- Two temperature sensors (one internal, one external)\n\nIn these cases:\n- The `measure` field will be the same (e.g., \"temperature\")\n- The `sensor_type` field will be different (e.g., \"in100-q1-r-rc0i\" vs \"sht40-ad1b-r2\")\n\nUse the `sensorType` parameter to filter a specific sensor when needed.\n\n## Rate Limiting\n\nAPIs are subject to rate limiting to ensure service stability.\nSpecific limits depend on your usage plan:\n\n**Standard Plan:**\n- **Rate**: 0.5 requests per second\n- **Burst**: 5 simultaneous requests\n- **Quota**: 100,000 requests per month\n\n**Custom Plans:**\nOther plans may be available with different limits. Contact support for more information.\n\n**⚠️ Important:** Be careful not to consume all your quota in the first three days of the month. Plan your API usage to distribute requests evenly throughout the billing period to avoid running out of quota early.\n\n## Date Format\n\nAll dates must be in **ISO 8601** format with UTC timezone:\n- Format: `YYYY-MM-DDTHH:mm:ssZ`\n- Example: `2026-02-04T15:30:00Z`\n\n## Pagination\n\nThe `/devices/{deviceId}/history` endpoint supports pagination:\n- `limit`: Maximum number of results (default: 100, max: 100)\n- `offset`: Number of results to skip (default: 0)\n- The response includes pagination information in the `pagination` field\n\n## Historical Data Granularity\n\nThe `/devices/{deviceId}/history` endpoint supports different granularities:\n- `raw`: Raw data without aggregation\n- `15m`: Data aggregated every 15 minutes (average, min, max)\n- `1h`: Data aggregated hourly (average, min, max)\n- `1d`: Daily aggregated data (average, min, max)\n\n## Data Availability\n\nHistorical data availability depends on the granularity and the sensor installation date:\n\n- **Data availability start date**: Data is available from the sensor installation date for each device\n- **Raw data (`raw`)**: Available for **30 days** from the current date\n- **High-frequency data CSV files**: Available for **30 days** from the current date (same retention as raw data)\n  - **Note:** This feature is only available if you have high-frequency devices (e.g., motor sensors). If your tenant/site doesn't have high-frequency devices, the API will return an error message.\n- **15-minute aggregated data (`15m`)**: Available for **90 days** from the current date\n- **Hourly aggregated data (`1h`)**: Available for **2 years** from the current date\n- **Daily aggregated data (`1d`)**: Available **permanently** (no expiration)\n\n**Recommendations:**\n- Use `raw` for recent detailed analysis (last 30 days)\n- Use high-frequency CSV files for bulk download of recent data (last 30 days)\n- Use `15m` for medium-term trends (up to 90 days)\n- Use `1h` for long-term analysis (up to 2 years)\n- Use `1d` for historical analysis beyond 2 years\n\n## HTTP Status Codes\n\n- `200 OK`: Request completed successfully\n- `400 Bad Request`: Malformed request or missing parameters\n- `401 Unauthorized`: Missing or invalid API key\n- `403 Forbidden`: Access denied\n- `404 Not Found`: Resource not found\n- `500 Internal Server Error`: Internal server error\n\n## Support\n\nFor assistance or questions, contact technical support.\n## Devices\n\nOperations to query available IoT devices. See the **Devices** section below.\n\n## Data\n\nOperations to access device data (latest values, bulk read, history). See the **Data** section below.\n\n## High Frequency Data\n\nHourly CSV downloads for high-frequency sensors. See the **High Frequency Data** section below.\n\n## Tags\n\nReusable labels to group devices. Filter with `GET /devices?tags=<uuid>`. See the **Tags** section below.\n\n## Alarms\n\nMonitor device conditions and manage open alarm instances. See the **Alarms** section below.\n",
    "version": "1.0.0",
    "contact": {
      "name": "EOS Support Team",
      "email": "software@sintropy.ai",
      "url": "https://sintropy.ai"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://sintropy.ai"
    }
  },
  "servers": [
    {
      "url": "https://p2uyaymt8c.execute-api.eu-south-1.amazonaws.com/v1/integration",
      "description": "Production server"
    }
  ],
  "tags": [
    {
      "name": "Devices",
      "description": "Operations to query available IoT devices.\n\nThese endpoints allow you to:\n- Get the list of devices (optionally filter by tag with `?tags=<uuid>`)\n- Query available measurements for each device"
    },
    {
      "name": "Data",
      "description": "Operations to access device data.\n\nThese endpoints allow you to:\n- Get the latest available value for a device\n- Get the latest values for multiple devices in a single call\n- Query historical data with different granularities\n- Filter by measurement type and sensor"
    },
    {
      "name": "High Frequency Data",
      "description": "Operations to access CSV files for high-frequency device data.\n\nThese endpoints allow you to:\n- List available high-frequency data CSV files\n- Download CSV files via presigned S3 URLs\n- Filter files by date, hour, and device\n- Track daily download quotas"
    },
    {
      "name": "Tags",
      "description": "Manage tags (labels applied to devices) and device↔tag associations.\n\n**Permissions:** reads require `devices:read`; create / rename / delete and (dis)association require `devices:command`.\n\n**Scope model.** Every tag has a scope: `global`, `tenant` (needs `tenantId`), or `site` (needs `tenantId` + `siteId`).\nVisibility and management follow the API key scope (GLOBAL / TENANT / SITE):\n- **GLOBAL** sees and manages every scope.\n- **TENANT** sees global + its own tenant (all levels); manages tenant/site of its own tenant.\n- **SITE** sees global + its tenant's tenant-level tags + its own site tags; manages only its own site scope.\n- **Associating / dissociating** a device needs only *visibility* of the tag (not management rights).\n\n**⚠️ Destructive operations.** `DELETE /tags/{tagId}` removes the tag **and every device\nassociation**, and any alarm definition that targets the tag will resolve to a different device set\n(possibly empty). Bulk `DELETE /tags/{tagId}/devices` detaches many devices in one call.\nCall `GET /tags/{tagId}/devices/count` first to see the blast radius. These actions cannot be undone."
    },
    {
      "name": "Alarms",
      "description": "Manage alarm **definitions** (the rules), read open **instances** and their **history**, and\nacknowledge/resolve instances.\n\n**Permissions:** reads require `devices:read`; create / update / delete definitions, device-rule pins,\nand instance ack/resolve require `devices:command`.\n\n**Scope & origin governance.**\n- Definitions carry a `scope` (`global` / `tenant` / `site`) and an `origin` (`system` | `user`).\n- `origin` defaults to `system` when created by a GLOBAL caller and to `user` otherwise; only GLOBAL\n  callers may create, update, delete, pin device-rules on, or ack/resolve **`system`** definitions and their instances.\n- `user` definitions must have a tenant or site scope (never global).\n- A GLOBAL caller creating a `user` definition with a tenant scope transfers ownership to that tenant.\n- `conditionType` and `scope` are **immutable** after creation (PATCH cannot change them).\n\n**Condition types.** `non_reporting` (device stopped sending telemetry, `thresholdMultiplier` × sampling\ninterval) and `threshold` (a measure series over/under a value for a fraction of a time window).\n\n**Target (`targetDevices`).** Either explicit (`{ \"deviceIds\": [...] }`) or rule-based\n(`{ \"siteIds\": [...] }` / `{ \"deviceTypes\": [...] }` / `{ \"tags\": [...] }`), or `{}` = all devices in scope.\nExplicit and rule-based dimensions are **mutually exclusive**. Per-device pins (`device-rules`, `include`/`exclude`)\nonly apply to rule-based targets.\n\n**⚠️ Destructive operations.** `DELETE /alarms/definitions/{definitionId}` deletes the rule and\ncloses **all its open instances** (blast radius can be many devices for rule-based targets); future alarms of that\ndefinition stop firing. `PATCH .../instances/{instanceId}` with `action: \"resolve\"` closes the instance\n(delete-on-resolve — it disappears from the open list and lives on only in history). These actions cannot be undone."
    }
  ],
  "paths": {
    "/devices": {
      "get": {
        "tags": [
          "Devices"
        ],
        "summary": "List devices",
        "description": "Returns the list of devices available for your API key.\n\n**Behavior:**\n- Without parameters: returns all available devices\n- With `includeMeasures=true`: includes available measurements for each device\n\n**Performance:**\n- Without `includeMeasures`: fast query, ideal for long lists\n- With `includeMeasures=true`: slower query, use only when necessary\n\n**Usage examples:**\n- Simple list: `GET /devices`\n- With measurements: `GET /devices?includeMeasures=true`\n\n**Filter by tags:**\n- `?tags=<uuid>` — OR (default)\n- `?tags=<uuid>&tagsMatch=all` — AND\n",
        "operationId": "getDevices",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "includeMeasures",
            "in": "query",
            "description": "If `true`, includes available measurements for each device in the `available_measures` field.\n\n**When to use it:**\n- When you need to know which measurements are available before making data calls\n- When you want to show users the available options\n\n**When NOT to use it:**\n- When you only want the device list (faster)\n- When you already have information about available measurements\n",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false,
              "example": true
            }
          },
          {
            "name": "tags",
            "in": "query",
            "description": "Comma-separated tag UUIDs. Returns devices with any listed tag (OR), unless tagsMatch=all.",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "tagsMatch",
            "in": "query",
            "description": "Set to `all` to require every listed tag (AND).",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "all"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Device list returned successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "devices": {
                      "type": "array",
                      "description": "List of accessible devices",
                      "items": {
                        "$ref": "#/components/schemas/Device"
                      }
                    }
                  }
                },
                "examples": {
                  "senza_misure": {
                    "summary": "Response without measurements (default)",
                    "value": {
                      "devices": [
                        {
                          "device_id": "aa:bb:cc:dd:ee:ff",
                          "label": "Sensore Magico della Temperatura"
                        },
                        {
                          "device_id": "11:22:33:44:55:66",
                          "label": "Termometro Felice"
                        },
                        {
                          "device_id": "ff:ee:dd:cc:bb:aa",
                          "label": "Umidimetro Saggio"
                        },
                        {
                          "device_id": "42:42:42:42:42:42",
                          "label": "Il Sensore della Risposta"
                        },
                        {
                          "device_id": "ca:fe:ba:be:de:ad",
                          "label": "Coffee Sensor (sempre caldo)"
                        }
                      ]
                    }
                  },
                  "con_misure": {
                    "summary": "Response with available measurements",
                    "value": {
                      "devices": [
                        {
                          "device_id": "aa:bb:cc:dd:ee:ff",
                          "label": "Sensore Magico della Temperatura",
                          "available_measures": [
                            {
                              "measure": "humidity",
                              "unit": "%",
                              "sensor_type": "dht22"
                            },
                            {
                              "measure": "temperature",
                              "unit": "C",
                              "sensor_type": "in100-q1-r-rc0i"
                            },
                            {
                              "measure": "temperature",
                              "unit": "C",
                              "sensor_type": "sht40-ad1b-r2"
                            }
                          ]
                        },
                        {
                          "device_id": "11:22:33:44:55:66",
                          "label": "Termometro Felice",
                          "available_measures": [
                            {
                              "measure": "temperature",
                              "unit": "C",
                              "sensor_type": "ds18b20"
                            }
                          ]
                        },
                        {
                          "device_id": "42:42:42:42:42:42",
                          "label": "Il Sensore della Risposta",
                          "available_measures": [
                            {
                              "measure": "temperature",
                              "unit": "C",
                              "sensor_type": "in100-q1-r-rc0i"
                            },
                            {
                              "measure": "humidity",
                              "unit": "%",
                              "sensor_type": "dht22"
                            }
                          ]
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/devices/{deviceId}/last": {
      "get": {
        "tags": [
          "Data"
        ],
        "summary": "Device last value",
        "description": "Returns the latest available value for a specific device.\n\n**Behavior based on parameters:**\n\n1. **Without parameters** (`GET /devices/{deviceId}/last`):\n   - Returns all measurements from the latest available timestamp\n   - Format: object with `device_id`, `timestamp` and `data` array\n   - Useful when you want to see all simultaneous measurements\n\n2. **With `measure`** (`GET /devices/{deviceId}/last?measure=temperature`):\n   - Returns only that measurement\n   - If there are multiple sensors with the same `measure`, returns all\n   - Format: single object or array (if multiple sensors)\n   - Useful when you're interested in a specific measurement\n\n3. **With `measure` and `sensorType`** (`GET /devices/{deviceId}/last?measure=temperature&sensorType=in100-q1-r-rc0i`):\n   - Returns only that specific sensor\n   - Format: single object\n   - Useful when there are multiple sensors of the same type and you want a specific one\n\n**Usage examples:**\n- All measurements: `GET /devices/aa:bb:cc:dd:ee:ff/last`\n- Only temperature: `GET /devices/aa:bb:cc:dd:ee:ff/last?measure=temperature`\n- Specific sensor: `GET /devices/aa:bb:cc:dd:ee:ff/last?measure=temperature&sensorType=in100-q1-r-rc0i`\n",
        "operationId": "getDeviceLastValue",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "deviceId",
            "in": "path",
            "required": true,
            "description": "Device ID (MAC address in `XX:XX:XX:XX:XX:XX` format).\n\n**Format:**\n- Separator: colon (`:`)\n- Case insensitive\n- Example: `aa:bb:cc:dd:ee:ff`\n",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-fA-F]{2}(:[0-9a-fA-F]{2}){5}$",
              "example": "aa:bb:cc:dd:ee:ff"
            }
          },
          {
            "name": "measure",
            "in": "query",
            "description": "Filter by measurement type.\n\n**Accepted values:**\n- Any measurement type returned by the sensors (e.g., `temperature`, `humidity`, `pressure`, `co2`, `battery_percentage`, `battery_voltage`, `rssi`, `a_current`, `b_current`, `c_current`, `total_current`, `a_voltage`, `b_voltage`, `c_voltage`, `a_act_power`, `b_act_power`, `c_act_power`, `total_act_power`, etc.)\n- `\"all\"`: returns all measurements (default behavior if omitted)\n\n**Behavior:**\n- If omitted or `\"all\"`: returns all measurements\n- If specified: returns only that measurement type\n- The value must match exactly the `measure` field returned by the sensors (case-sensitive)\n",
            "required": false,
            "schema": {
              "type": "string",
              "example": "temperature"
            }
          },
          {
            "name": "sensorType",
            "in": "query",
            "description": "Filter by sensor type.\n\n**When to use it:**\n- When there are multiple sensors with the same `measure`\n- When you want a specific sensor\n\n**Examples:**\n- `in100-q1-r-rc0i`: Internal temperature sensor\n- `sht40-ad1b-r2`: External temperature sensor\n- `dht22`: Humidity/temperature sensor\n\n**Note:** Must be used together with `measure` to be effective.\n",
            "required": false,
            "schema": {
              "type": "string",
              "example": "in100-q1-r-rc0i"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Latest value(s) returned successfully",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/DeviceDataSingle"
                    },
                    {
                      "$ref": "#/components/schemas/DeviceDataMultiple"
                    }
                  ]
                },
                "examples": {
                  "singola_misurazione": {
                    "summary": "Single measurement (with measure and sensorType)",
                    "description": "When you specify both measure and sensorType, you get a single object",
                    "value": {
                      "timestamp": "2026-02-04T15:30:00Z",
                      "device_id": "aa:bb:cc:dd:ee:ff",
                      "sensor_type": "in100-q1-r-rc0i",
                      "measure": "temperature",
                      "unit": "C",
                      "value": 22.5
                    }
                  },
                  "misurazione_singola_measure": {
                    "summary": "Measurement with only measure (may be array if multiple sensors)",
                    "description": "If you specify only measure and there are multiple sensors, you get an array",
                    "value": {
                      "device_id": "aa:bb:cc:dd:ee:ff",
                      "timestamp": "2026-02-04T15:30:00Z",
                      "data": [
                        {
                          "timestamp": "2026-02-04T15:30:00Z",
                          "device_id": "aa:bb:cc:dd:ee:ff",
                          "sensor_type": "in100-q1-r-rc0i",
                          "measure": "temperature",
                          "unit": "C",
                          "value": 22.5
                        },
                        {
                          "timestamp": "2026-02-04T15:30:00Z",
                          "device_id": "aa:bb:cc:dd:ee:ff",
                          "sensor_type": "sht40-ad1b-r2",
                          "measure": "temperature",
                          "unit": "C",
                          "value": 23.1
                        }
                      ]
                    }
                  },
                  "tutte_misure": {
                    "summary": "All measurements (without parameters)",
                    "description": "When you don't specify parameters, you get all measurements from the latest timestamp",
                    "value": {
                      "device_id": "aa:bb:cc:dd:ee:ff",
                      "timestamp": "2026-02-04T15:30:00Z",
                      "data": [
                        {
                          "timestamp": "2026-02-04T15:30:00Z",
                          "device_id": "aa:bb:cc:dd:ee:ff",
                          "sensor_type": "dht22",
                          "measure": "humidity",
                          "unit": "%",
                          "value": 45.2
                        },
                        {
                          "timestamp": "2026-02-04T15:30:00Z",
                          "device_id": "aa:bb:cc:dd:ee:ff",
                          "sensor_type": "in100-q1-r-rc0i",
                          "measure": "temperature",
                          "unit": "C",
                          "value": 22.5
                        },
                        {
                          "timestamp": "2026-02-04T15:30:00Z",
                          "device_id": "aa:bb:cc:dd:ee:ff",
                          "sensor_type": "sht40-ad1b-r2",
                          "measure": "temperature",
                          "unit": "C",
                          "value": 23.1
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/devices/last/bulk": {
      "post": {
        "tags": [
          "Data"
        ],
        "summary": "Multiple devices last values",
        "description": "Returns the latest values for multiple devices in a single call.\n\n**Advantages:**\n- Reduces the number of HTTP calls needed\n- More efficient when reading data from many devices\n- Maintains the same filter logic as `/devices/{deviceId}/last`\n\n**Limitations:**\n- Maximum 100 devices per request\n- Longer timeout for large lists\n\n**Behavior:**\n- Without `measure`: returns all measurements for each device\n- With `measure`: returns only that measurement for each device\n- With `measure` and `sensorType`: returns only that sensor for each device\n\n**Response format:**\n- Each element in the `results` array can be a single object (if measure+sensorType) or an object with `data` array (if multiple measurements)\n\n**Usage examples:**\n- All devices, all measurements: `POST /devices/last/bulk` with `{\"deviceIds\": [...]}`\n- All devices, only temperature: `POST /devices/last/bulk` with `{\"deviceIds\": [...], \"measure\": \"temperature\"}`\n- Specific sensor: `POST /devices/last/bulk` with `{\"deviceIds\": [...], \"measure\": \"temperature\", \"sensorType\": \"in100-q1-r-rc0i\"}`\n",
        "operationId": "getDevicesLastValueBulk",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "description": "Request with device ID list and optional filters",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BulkRequest"
              },
              "examples": {
                "tutte_misure": {
                  "summary": "All measurements for all devices",
                  "description": "Base request without filters",
                  "value": {
                    "deviceIds": [
                      "aa:bb:cc:dd:ee:ff",
                      "11:22:33:44:55:66",
                      "ff:ee:dd:cc:bb:aa"
                    ]
                  }
                },
                "misura_specifica": {
                  "summary": "Only temperature for all devices",
                  "description": "Filter by measurement type",
                  "value": {
                    "deviceIds": [
                      "aa:bb:cc:dd:ee:ff",
                      "11:22:33:44:55:66"
                    ],
                    "measure": "temperature"
                  }
                },
                "sensore_specifico": {
                  "summary": "Specific sensor for all devices",
                  "description": "Filter by measurement and sensor",
                  "value": {
                    "deviceIds": [
                      "aa:bb:cc:dd:ee:ff",
                      "11:22:33:44:55:66"
                    ],
                    "measure": "temperature",
                    "sensorType": "in100-q1-r-rc0i"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Latest values returned successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BulkResponse"
                },
                "examples": {
                  "esempio_completo": {
                    "summary": "Response with multiple devices",
                    "value": {
                      "results": [
                        {
                          "device_id": "aa:bb:cc:dd:ee:ff",
                          "timestamp": "2026-02-04T15:30:00Z",
                          "data": [
                            {
                              "timestamp": "2026-02-04T15:30:00Z",
                              "device_id": "aa:bb:cc:dd:ee:ff",
                              "sensor_type": "in100-q1-r-rc0i",
                              "measure": "temperature",
                              "unit": "C",
                              "value": 22.5
                            },
                            {
                              "timestamp": "2026-02-04T15:30:00Z",
                              "device_id": "aa:bb:cc:dd:ee:ff",
                              "sensor_type": "sht40-ad1b-r2",
                              "measure": "temperature",
                              "unit": "C",
                              "value": 23.1
                            }
                          ]
                        },
                        {
                          "timestamp": "2026-02-04T15:30:00Z",
                          "device_id": "11:22:33:44:55:66",
                          "sensor_type": "ds18b20",
                          "measure": "temperature",
                          "unit": "C",
                          "value": 21.8
                        }
                      ],
                      "count": 2,
                      "requested": 2
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "deviceIds_mancante": {
                    "summary": "Missing deviceIds",
                    "value": {
                      "error": "Bad Request",
                      "message": "Missing or invalid deviceIds array in request body"
                    }
                  },
                  "troppi_device": {
                    "summary": "Too many devices",
                    "value": {
                      "error": "TOO_MANY_DEVICE_IDS",
                      "message": "Max 100 deviceIds per request."
                    }
                  },
                  "array_vuoto": {
                    "summary": "Empty array",
                    "value": {
                      "error": "Bad Request",
                      "message": "deviceIds array cannot be empty"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/devices/{deviceId}/history": {
      "get": {
        "tags": [
          "Data"
        ],
        "summary": "Device historical data",
        "description": "Returns historical data for a device within a specified time range.\n\n**Available granularities:**\n\n1. **`raw`** (default):\n   - Raw data without aggregation\n   - Each row represents a single measurement\n   - Maximum temporal precision\n   - More data returned\n\n2. **`15m`**:\n   - Data aggregated every 15 minutes\n   - Each row contains: average, minimum, maximum\n   - Significantly reduces data volume\n   - Useful for medium-term visualizations\n\n3. **`1h`**:\n   - Data aggregated hourly\n   - Each row contains: average, minimum, maximum\n   - Even more compact\n   - Useful for daily/weekly analysis\n\n4. **`1d`**:\n   - Daily aggregated data\n   - Each row contains: average, minimum, maximum for the day\n   - Minimum data volume\n   - Useful for long-term analysis\n\n**Pagination:**\n- Use `limit` to control how many results to get (max 100)\n- Use `offset` to navigate between pages\n- The response includes `pagination.hasMore` to know if there's more data\n\n**Filters:**\n- `measure`: Filter by measurement type\n- `sensorType`: Filter by sensor type (must be used with measure)\n\n**Usage examples:**\n- Complete history: `GET /devices/{id}/history?startDate=...&endDate=...`\n- Only temperature: `GET /devices/{id}/history?startDate=...&endDate=...&measure=temperature`\n- Specific sensor: `GET /devices/{id}/history?startDate=...&endDate=...&measure=temperature&sensorType=in100-q1-r-rc0i`\n- Aggregated data: `GET /devices/{id}/history?startDate=...&endDate=...&granularity=1d`\n",
        "operationId": "getDeviceHistory",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "deviceId",
            "in": "path",
            "required": true,
            "description": "Device ID (MAC address)",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-fA-F]{2}(:[0-9a-fA-F]{2}){5}$",
              "example": "aa:bb:cc:dd:ee:ff"
            }
          },
          {
            "name": "startDate",
            "in": "query",
            "required": true,
            "description": "Start date/time of the range (ISO 8601, UTC).\n\n**Format:** `YYYY-MM-DDTHH:mm:ssZ`\n**Example:** `2026-02-04T00:00:00Z`\n",
            "schema": {
              "type": "string",
              "format": "date-time",
              "example": "2026-02-04T00:00:00Z"
            }
          },
          {
            "name": "endDate",
            "in": "query",
            "required": true,
            "description": "End date/time of the range (ISO 8601, UTC).\n\n**Format:** `YYYY-MM-DDTHH:mm:ssZ`\n**Example:** `2026-02-04T23:59:59Z`\n\n**Note:** Must be after `startDate`.\n",
            "schema": {
              "type": "string",
              "format": "date-time",
              "example": "2026-02-04T23:59:59Z"
            }
          },
          {
            "name": "granularity",
            "in": "query",
            "required": false,
            "description": "Granularity of returned data.\n\n**Values:**\n- `raw`: Raw data (default)\n- `15m`: Aggregated every 15 minutes\n- `1h`: Aggregated hourly\n- `1d`: Daily aggregated\n",
            "schema": {
              "type": "string",
              "enum": [
                "raw",
                "15m",
                "1h",
                "1d"
              ],
              "default": "raw",
              "example": "raw"
            }
          },
          {
            "name": "measure",
            "in": "query",
            "required": false,
            "description": "Filter by measurement type",
            "schema": {
              "type": "string",
              "example": "temperature"
            }
          },
          {
            "name": "sensorType",
            "in": "query",
            "required": false,
            "description": "Filter by sensor type (must be used with measure)",
            "schema": {
              "type": "string",
              "example": "in100-q1-r-rc0i"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of results to return.\n\n**Range:** 1-100\n**Default:** 100\n",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 100,
              "example": 100
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Number of results to skip (for pagination).\n\n**Range:** >= 0\n**Default:** 0\n",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0,
              "example": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Historical data returned successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HistoryResponse"
                },
                "examples": {
                  "dati_raw": {
                    "summary": "Raw data (granularity=raw)",
                    "value": {
                      "data": [
                        {
                          "timestamp": "2026-02-04T00:00:00Z",
                          "device_id": "aa:bb:cc:dd:ee:ff",
                          "sensor_type": "in100-q1-r-rc0i",
                          "measure": "temperature",
                          "unit": "C",
                          "value": 22.1
                        },
                        {
                          "timestamp": "2026-02-04T00:15:00Z",
                          "device_id": "aa:bb:cc:dd:ee:ff",
                          "sensor_type": "in100-q1-r-rc0i",
                          "measure": "temperature",
                          "unit": "C",
                          "value": 22.3
                        },
                        {
                          "timestamp": "2026-02-04T00:30:00Z",
                          "device_id": "aa:bb:cc:dd:ee:ff",
                          "sensor_type": "in100-q1-r-rc0i",
                          "measure": "temperature",
                          "unit": "C",
                          "value": 22.5
                        }
                      ],
                      "pagination": {
                        "limit": 100,
                        "offset": 0,
                        "total": 96,
                        "hasMore": true,
                        "returned": 96
                      },
                      "granularity": "raw"
                    }
                  },
                  "dati_aggregati": {
                    "summary": "Aggregated data (granularity=1d)",
                    "value": {
                      "data": [
                        {
                          "timestamp": "2026-02-04T00:00:00Z",
                          "device_id": "aa:bb:cc:dd:ee:ff",
                          "sensor_type": "in100-q1-r-rc0i",
                          "measure": "temperature",
                          "unit": "C",
                          "avg_value": 22.3,
                          "max_value": 24.1,
                          "min_value": 20.5
                        },
                        {
                          "timestamp": "2026-02-05T00:00:00Z",
                          "device_id": "aa:bb:cc:dd:ee:ff",
                          "sensor_type": "in100-q1-r-rc0i",
                          "measure": "temperature",
                          "unit": "C",
                          "avg_value": 23.1,
                          "max_value": 25.2,
                          "min_value": 21
                        }
                      ],
                      "pagination": {
                        "limit": 100,
                        "offset": 0,
                        "total": 30,
                        "hasMore": false,
                        "returned": 30
                      },
                      "granularity": "1d"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "date_mancanti": {
                    "summary": "Missing dates",
                    "value": {
                      "error": "Bad Request",
                      "message": "Missing required query parameters: startDate and endDate"
                    }
                  },
                  "granularity_invalida": {
                    "summary": "Invalid granularity",
                    "value": {
                      "error": "Bad Request",
                      "message": "Invalid granularity. Must be one of: raw, 15m, 1h, 1d"
                    }
                  },
                  "limit_invalido": {
                    "summary": "Invalid limit",
                    "value": {
                      "error": "Bad Request",
                      "message": "Invalid limit parameter. Must be a positive integer."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/backup_files_highfrequency": {
      "get": {
        "tags": [
          "High Frequency Data"
        ],
        "summary": "List high-frequency data CSV files",
        "description": "Returns a list of available CSV files for high-frequency device data with presigned S3 URLs for download.\n\n**⚠️ Important:** This endpoint is only available if you have high-frequency devices (e.g., motor sensors). If your tenant/site doesn't have high-frequency devices, the API will return an error message `NoHighFrequencyDevices`.\n\n**High-Frequency Data System:**\n- CSV files are automatically created every hour (at minute 10) for the previous hour\n- One file per device per hour (only if data exists)\n- Files are stored in S3 and organized by tenant, device, date, and hour\n- Only devices marked as `highfrequency = true` are included\n- **Data Retention:** High-frequency data CSV files are available for 30 days (same as raw data)\n\n**Download Quotas:**\n- Each tenant has a daily download limit (`backup_hf_daily_download_limit`)\n- The limit is shared across all API keys/users within the tenant\n- Quota resets at midnight UTC\n- Each presigned URL generation counts toward the daily limit\n\n**Default behavior (no `deviceId` and no `hour`):**\n- Returns **only the latest available file for each device** (for the selected `date`, default: current day UTC)\n- This keeps responses small and is ideal for \"daily sync\" clients\n\n**Full-day mode:**\n- To retrieve **all files for the day across all devices**, use `mode=day`\n\n**Filtering:**\n- `date`: Filter by specific date (YYYY-MM-DD). Default: current day\n- `hour`: Filter by specific hour (0-23)\n- `deviceId`: Filter by specific device (must belong to your tenant)\n- `mode`: `latest` (default) or `day`\n\n**Response Format:**\n- Files are grouped by `device_id`, then by `hour`\n- Each hour contains a presigned S3 URL (valid for 1 hour)\n- Quota information shows daily limit, usage, and remaining downloads\n\n**Error Cases:**\n- If tenant/site has no high-frequency devices: returns `error: \"NoHighFrequencyDevices\"`\n- If daily quota exceeded: returns `429 Too Many Requests`\n- If device doesn't belong to tenant: returns `403 Forbidden`\n\n**Note:** High-frequency data CSV files are available for 30 days (same retention period as raw data).\n",
        "operationId": "listBackupFiles",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "date",
            "in": "query",
            "required": false,
            "description": "Date to query high-frequency data files (YYYY-MM-DD format).\n\n**Default:** Current day (UTC)\n**Example:** `2026-02-19`\n",
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "example": "2026-02-19"
            }
          },
          {
            "name": "hour",
            "in": "query",
            "required": false,
            "description": "Hour to filter files (0-23).\n\n**Example:** `15` for 3 PM\n",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 23,
              "example": 15
            }
          },
          {
            "name": "deviceId",
            "in": "query",
            "required": false,
            "description": "Device ID to filter files (MAC address format).\n\n**Note:** Device must belong to your tenant\n**Example:** `ad:6a:f5:ae:c9:b5`\n",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-fA-F]{2}(:[0-9a-fA-F]{2}){5}$",
              "example": "ad:6a:f5:ae:c9:b5"
            }
          },
          {
            "name": "mode",
            "in": "query",
            "required": false,
            "description": "Response mode:\n- `latest` (default): returns only the latest available file for each device (for the selected date)\n- `day`: returns all files for the whole day (may be large)\n",
            "schema": {
              "type": "string",
              "enum": [
                "latest",
                "day"
              ],
              "default": "latest",
              "example": "latest"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "High-frequency data files list returned successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "files": {
                      "type": "object",
                      "description": "Files grouped by device_id, then by hour",
                      "additionalProperties": {
                        "type": "object",
                        "description": "Hours for this device",
                        "additionalProperties": {
                          "type": "string",
                          "format": "uri",
                          "description": "Presigned S3 URL (valid for 1 hour)"
                        }
                      },
                      "example": {
                        "ad:6a:f5:ae:c9:b5": {
                          "14": "https://s3.amazonaws.com/bucket/backups/tenant_10/device_ad:6a:f5:ae:c9:b5/2026/02/19/14/backup_2026-02-19_14-02-43.csv?X-Amz-Algorithm=...",
                          "15": "https://s3.amazonaws.com/bucket/backups/tenant_10/device_ad:6a:f5:ae:c9:b5/2026/02/19/15/backup_2026-02-19_15-02-43.csv?X-Amz-Algorithm=..."
                        }
                      }
                    },
                    "quota": {
                      "type": "object",
                      "description": "Daily download quota information",
                      "properties": {
                        "dailyLimit": {
                          "type": "integer",
                          "description": "Daily download limit for the tenant",
                          "example": 100
                        },
                        "usedToday": {
                          "type": "integer",
                          "description": "Number of downloads used today",
                          "example": 3
                        },
                        "remaining": {
                          "type": "integer",
                          "description": "Remaining downloads for today",
                          "example": 97
                        },
                        "resetAt": {
                          "type": "string",
                          "format": "date-time",
                          "description": "When the quota resets (midnight UTC)",
                          "example": "2026-02-20T00:00:00.000Z"
                        }
                      }
                    }
                  }
                },
                "examples": {
                  "success": {
                    "summary": "Success with files",
                    "value": {
                      "files": {
                        "ad:6a:f5:ae:c9:b5": {
                          "14": "https://s3.amazonaws.com/bucket/backups/tenant_10/device_ad:6a:f5:ae:c9:b5/2026/02/19/14/backup_2026-02-19_14-02-43.csv?X-Amz-Algorithm=...",
                          "15": "https://s3.amazonaws.com/bucket/backups/tenant_10/device_ad:6a:f5:ae:c9:b5/2026/02/19/15/backup_2026-02-19_15-02-43.csv?X-Amz-Algorithm=..."
                        }
                      },
                      "quota": {
                        "dailyLimit": 100,
                        "usedToday": 3,
                        "remaining": 97,
                        "resetAt": "2026-02-20T00:00:00.000Z"
                      }
                    }
                  },
                  "no_files": {
                    "summary": "No files found (but has high-frequency devices)",
                    "value": {
                      "files": {},
                      "quota": {
                        "dailyLimit": 100,
                        "usedToday": 0,
                        "remaining": 100,
                        "resetAt": "2026-02-20T00:00:00.000Z"
                      }
                    }
                  },
                  "no_hf_devices": {
                    "summary": "Tenant has no high-frequency devices",
                    "value": {
                      "error": "NoHighFrequencyDevices",
                      "message": "You don't have high frequency devices for tenant 10",
                      "files": {},
                      "quota": {
                        "dailyLimit": 100,
                        "usedToday": 0,
                        "remaining": 100,
                        "resetAt": "2026-02-20T00:00:00.000Z"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "invalid_date": {
                    "summary": "Invalid date format",
                    "value": {
                      "error": "Invalid date format. Use YYYY-MM-DD"
                    }
                  },
                  "invalid_hour": {
                    "summary": "Invalid hour",
                    "value": {
                      "error": "Invalid hour. Must be between 0 and 23"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Forbidden - Missing permission or device doesn't belong to tenant",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "missing_permission": {
                    "summary": "Missing permission",
                    "value": {
                      "error": "Forbidden",
                      "message": "Missing required permission: devices:read"
                    }
                  },
                  "device_not_belongs": {
                    "summary": "Device doesn't belong to tenant",
                    "value": {
                      "error": "Forbidden",
                      "message": "Device ad:6a:f5:ae:c9:b5 does not belong to your tenant"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Device not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "NotFound",
                  "message": "Device not found"
                }
              }
            }
          },
          "429": {
            "description": "Daily download quota exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "example": "QuotaExceeded"
                    },
                    "message": {
                      "type": "string",
                      "example": "Daily download limit reached. Limit: 100, Used: 100"
                    },
                    "quota": {
                      "type": "object",
                      "properties": {
                        "dailyLimit": {
                          "type": "integer",
                          "example": 100
                        },
                        "usedToday": {
                          "type": "integer",
                          "example": 100
                        },
                        "remaining": {
                          "type": "integer",
                          "example": 0
                        },
                        "resetAt": {
                          "type": "string",
                          "format": "date-time",
                          "example": "2026-02-20T00:00:00.000Z"
                        }
                      }
                    }
                  }
                },
                "example": {
                  "error": "QuotaExceeded",
                  "message": "Daily download limit reached. Limit: 100, Used: 100",
                  "quota": {
                    "dailyLimit": 100,
                    "usedToday": 100,
                    "remaining": 0,
                    "resetAt": "2026-02-20T00:00:00.000Z"
                  }
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/tags": {
      "get": {
        "tags": [
          "Tags"
        ],
        "summary": "List tags",
        "description": "Lists tags visible to your API key scope. Supports free-text search and scope filtering.",
        "operationId": "integrationListTags",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Case-insensitive substring match on the tag label (autocomplete / duplicate detection).",
            "schema": {
              "type": "string",
              "example": "frigo"
            }
          },
          {
            "name": "scope",
            "in": "query",
            "required": false,
            "description": "Filter by scope level.",
            "schema": {
              "type": "string",
              "enum": [
                "global",
                "tenant",
                "site"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 100
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Tag list",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TagListResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      },
      "post": {
        "tags": [
          "Tags"
        ],
        "summary": "Create a tag",
        "description": "Creates a tag at the requested scope. Requires `devices:command`.\nYour API key scope must be able to manage the requested scope (GLOBAL ≥ TENANT ≥ SITE).",
        "operationId": "integrationCreateTag",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateTagRequest"
              },
              "examples": {
                "tenant_tag": {
                  "summary": "Tenant-scoped tag",
                  "value": {
                    "label": "frigo",
                    "scope": {
                      "level": "tenant",
                      "tenantId": 10
                    }
                  }
                },
                "global_tag": {
                  "summary": "Global tag (GLOBAL scope only)",
                  "value": {
                    "label": "critici",
                    "scope": {
                      "level": "global"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Tag created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TagResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/tags/{tagId}": {
      "get": {
        "tags": [
          "Tags"
        ],
        "summary": "Get a tag by id",
        "operationId": "integrationGetTag",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "tagId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Tag detail",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TagResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      },
      "patch": {
        "tags": [
          "Tags"
        ],
        "summary": "Rename a tag",
        "description": "Updates the tag label. Requires `devices:command` and management rights over the tag's scope.",
        "operationId": "integrationUpdateTag",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "tagId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateTagRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Tag updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TagResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      },
      "delete": {
        "tags": [
          "Tags"
        ],
        "summary": "Delete a tag",
        "description": "⚠️ **Destructive — cannot be undone.** Deletes the tag **and every device association**.\nAny alarm definition that targets this tag will resolve to a different (possibly empty) device set.\nRequires `devices:command` and management rights over the tag's scope.\nTip: call `GET /tags/{tagId}/devices/count` first to see how many devices are affected.",
        "operationId": "integrationDeleteTag",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "tagId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Tag deleted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TagDeletedResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/tags/{tagId}/devices": {
      "get": {
        "tags": [
          "Tags"
        ],
        "summary": "List devices associated with a tag",
        "description": "Returns the device IDs currently associated with the tag, scoped to your API key visibility.",
        "operationId": "integrationListTagDevices",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "tagId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 100
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Device IDs for the tag",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TagDevicesListResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      },
      "post": {
        "tags": [
          "Tags"
        ],
        "summary": "Associate devices with a tag (bulk, integration)",
        "description": "Associates one or more devices with the tag. Idempotent (already-associated devices are skipped).\nRequires `devices:command` and only *visibility* of the tag.\n**All-or-nothing:** if any device is outside your scope or not applicable, the call returns `400`\nwith a `rejected` array and makes no changes.",
        "operationId": "integrationAssociateTagDevices",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "tagId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DeviceIdsRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Devices associated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AssociateDevicesResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/RejectedAssociation"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      },
      "delete": {
        "tags": [
          "Tags"
        ],
        "summary": "Dissociate devices from a tag (bulk, integration)",
        "description": "⚠️ **Bulk detach.** Removes the association between the tag and each listed device (the devices and\nthe tag are **not** deleted). Requires `devices:command` and tag visibility.\nAll-or-nothing: out-of-scope devices cause a `400` with a `rejected` array and no changes.",
        "operationId": "integrationDissociateTagDevices",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "tagId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DeviceIdsRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Devices dissociated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DissociateResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/RejectedAssociation"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/tags/{tagId}/devices/count": {
      "get": {
        "tags": [
          "Tags"
        ],
        "summary": "Count devices associated with a tag",
        "description": "Returns the number of devices associated with the tag within your scope.\nCheap `count(*)` — use it to assess the blast radius before renaming or deleting a tag.",
        "operationId": "integrationCountTagDevices",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "tagId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Device count",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TagDeviceCountResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/tags/{tagId}/devices/{deviceId}": {
      "delete": {
        "tags": [
          "Tags"
        ],
        "summary": "Dissociate a single device from a tag",
        "description": "Removes one device↔tag association. Requires `devices:command` and tag visibility.",
        "operationId": "integrationDissociateTagDevice",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "tagId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "deviceId",
            "in": "path",
            "required": true,
            "description": "Device MAC (URL-encode colons when needed).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Device dissociated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DissociateResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/devices/{deviceId}/tags": {
      "get": {
        "tags": [
          "Tags"
        ],
        "summary": "List tags on a device",
        "operationId": "integrationListDeviceTags",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "deviceId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Tags on the device",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeviceTagsResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      },
      "post": {
        "tags": [
          "Tags"
        ],
        "summary": "Associate tags with a device",
        "description": "Device-centric association: attaches the listed tags to the device. Idempotent.\nRequires `devices:command` and visibility of each tag. All-or-nothing on rejects (`400`).",
        "operationId": "integrationSetDeviceTags",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "deviceId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TagIdsRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Tags associated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AssociateTagsResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/RejectedAssociation"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/devices/{deviceId}/tags/{tagId}": {
      "delete": {
        "tags": [
          "Tags"
        ],
        "summary": "Dissociate a single tag from a device",
        "operationId": "integrationDeleteDeviceTag",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "deviceId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "tagId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Tag dissociated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DissociateResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/alarms/instances": {
      "get": {
        "tags": [
          "Alarms"
        ],
        "summary": "List open alarm instances",
        "description": "Returns currently open (`active` / `acknowledged`) alarm instances visible to your scope.",
        "operationId": "integrationListAlarmInstances",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "siteId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "deviceId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Open instances",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AlarmInstancesListResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/alarms/instances/{instanceId}": {
      "patch": {
        "tags": [
          "Alarms"
        ],
        "summary": "Acknowledge or resolve an alarm instance",
        "description": "- `action: \"acknowledge\"` marks an `active` instance as `acknowledged` (stops re-notification) and\n  returns the updated instance. Acknowledging a non-active instance returns `409`.\n- ⚠️ `action: \"resolve\"` **closes** the instance (delete-on-resolve): it disappears from the open list\n  and survives only in history. Returns `204 No Content`. Cannot be undone.\n\nRequires `devices:command`; instances of `system` definitions can only be managed by GLOBAL callers.",
        "operationId": "integrationPatchAlarmInstance",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "instanceId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PatchInstanceRequest"
              },
              "examples": {
                "acknowledge": {
                  "summary": "Acknowledge",
                  "value": {
                    "action": "acknowledge",
                    "note": "verified by the team"
                  }
                },
                "resolve": {
                  "summary": "Resolve (closes the instance)",
                  "value": {
                    "action": "resolve",
                    "note": "fixed on site"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Instance acknowledged",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AlarmInstanceResponse"
                }
              }
            }
          },
          "204": {
            "description": "Instance resolved (closed)"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/alarms/history": {
      "get": {
        "tags": [
          "Alarms"
        ],
        "summary": "List alarm history",
        "description": "Returns the lifecycle transitions (`active` / `acknowledged` / `resolved` / `cancelled`) visible to your scope.",
        "operationId": "integrationListAlarmHistory",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 100
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          },
          {
            "name": "siteId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "deviceId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "definitionId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "ISO-8601 lower bound (inclusive) on `transitionedAt`.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "ISO-8601 upper bound (inclusive) on `transitionedAt`.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "History entries",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AlarmHistoryListResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/alarms/definitions": {
      "get": {
        "tags": [
          "Alarms"
        ],
        "summary": "List alarm definitions",
        "description": "Lists alarm definitions visible to your API key scope.",
        "operationId": "integrationListAlarmDefinitions",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "siteId",
            "in": "query",
            "required": false,
            "description": "Narrow the list to a specific site.",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Definitions list",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AlarmDefinitionsListResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      },
      "post": {
        "tags": [
          "Alarms"
        ],
        "summary": "Create an alarm definition",
        "description": "Creates an alarm rule. Requires `devices:command` and management rights over the requested scope.\n`origin` defaults to `system` for GLOBAL callers and `user` otherwise; only GLOBAL callers can create\n`system` definitions. `user` definitions require a tenant or site scope.",
        "operationId": "integrationCreateAlarmDefinition",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateAlarmDefinitionRequest"
              },
              "examples": {
                "non_reporting_tenant": {
                  "summary": "Non-reporting rule over all devices in a tenant",
                  "value": {
                    "label": "Device offline",
                    "conditionType": "non_reporting",
                    "conditionConfiguration": {
                      "thresholdMultiplier": 3
                    },
                    "scope": {
                      "level": "tenant",
                      "tenantId": 10
                    }
                  }
                },
                "threshold_single_device": {
                  "summary": "Threshold rule on a single device",
                  "value": {
                    "label": "Freezer too warm",
                    "conditionType": "threshold",
                    "conditionConfiguration": {
                      "series": "temperature",
                      "direction": "over",
                      "threshold": -18,
                      "tolerance": 0,
                      "durationMs": 3600000,
                      "percentage": 1
                    },
                    "targetDevices": {
                      "deviceIds": [
                        "aa:bb:cc:dd:ee:ff"
                      ]
                    },
                    "scope": {
                      "level": "site",
                      "tenantId": 10,
                      "siteId": 3
                    }
                  }
                },
                "threshold_by_tag": {
                  "summary": "Threshold rule targeting a tag (rule-based)",
                  "value": {
                    "label": "Fridges too warm",
                    "conditionType": "threshold",
                    "conditionConfiguration": {
                      "series": "temperature",
                      "direction": "over",
                      "threshold": 8,
                      "tolerance": 0.5,
                      "durationMs": 1800000,
                      "percentage": 0.8
                    },
                    "targetDevices": {
                      "tags": [
                        "3fa85f64-5717-4562-b3fc-2c963f66afa6"
                      ]
                    },
                    "scope": {
                      "level": "tenant",
                      "tenantId": 10
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Definition created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AlarmDefinitionResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/alarms/definitions/{definitionId}": {
      "patch": {
        "tags": [
          "Alarms"
        ],
        "summary": "Update an alarm definition",
        "description": "Updates a definition. At least one of `label`, `conditionConfiguration`, `targetDevices`, `enabled`\nmust be provided. `conditionType` and `scope` are **immutable**. Requires `devices:command`;\n`system` definitions can only be updated by GLOBAL callers.",
        "operationId": "integrationUpdateAlarmDefinition",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "definitionId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateAlarmDefinitionRequest"
              },
              "examples": {
                "disable": {
                  "summary": "Disable a rule",
                  "value": {
                    "enabled": false
                  }
                },
                "rename": {
                  "summary": "Rename a rule",
                  "value": {
                    "label": "New label"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Definition updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AlarmDefinitionResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      },
      "delete": {
        "tags": [
          "Alarms"
        ],
        "summary": "Delete an alarm definition",
        "description": "⚠️ **Destructive — cannot be undone.** Deletes the rule and **closes all its open instances**.\nFor rule-based targets the blast radius can be many devices. Future alarms of this definition stop firing.\nRequires `devices:command`; `system` definitions can only be deleted by GLOBAL callers.",
        "operationId": "integrationDeleteAlarmDefinition",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "definitionId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Definition deleted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AlarmDefinitionDeletedResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/alarms/definitions/{definitionId}/devices": {
      "get": {
        "tags": [
          "Alarms"
        ],
        "summary": "Preview the resolved device set of a definition",
        "description": "Returns the devices the definition currently resolves to (after tag expansion, site/deviceType filters\nand `include`/`exclude` pins). Useful to verify a rule before enabling it.",
        "operationId": "integrationListAlarmDefinitionDevices",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "definitionId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Resolved device set",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PreviewDevicesResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/alarms/definitions/{definitionId}/device-rules": {
      "put": {
        "tags": [
          "Alarms"
        ],
        "summary": "Pin a device include/exclude on a rule-based definition",
        "description": "Upserts a per-device override: `include` forces a device into the set even if the rule doesn't match it,\n`exclude` removes it even if the rule matches. Only valid for **rule-based** targets — returns `400` on\nexplicit `deviceIds` targets. Requires `devices:command` (GLOBAL for `system` definitions).",
        "operationId": "integrationPutAlarmDeviceRule",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "definitionId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DeviceRuleRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Device rule upserted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeviceRuleResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/alarms/definitions/{definitionId}/device-rules/{deviceId}": {
      "delete": {
        "tags": [
          "Alarms"
        ],
        "summary": "Remove a device pin from a definition",
        "operationId": "integrationDeleteAlarmDeviceRule",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "definitionId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "deviceId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Pin removed (idempotent)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeviceRuleDeletedResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/devices/{deviceId}/alarms": {
      "get": {
        "tags": [
          "Alarms"
        ],
        "summary": "List alarm definitions that apply to a device",
        "description": "Returns the enabled definitions whose resolved device set includes this device (respecting your scope).",
        "operationId": "integrationListDeviceAlarms",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "deviceId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Definitions covering the device",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeviceAlarmsResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "x-api-key",
        "description": "Your API key for authentication.\n\n**How to obtain it:**\n1. Contact support to register your integration\n2. You will receive a unique API key\n3. Use it in all requests in the `x-api-key` header\n\n**Security:**\n- Never share your API key\n- Do not commit it to source code\n- Use environment variables to store it\n- If compromised, contact support immediately\n"
      },
      "BearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "Cognito **access token**.\n\nUse in header:\n- `Authorization: Bearer <ACCESS_TOKEN>`\n"
      }
    },
    "schemas": {
      "Device": {
        "type": "object",
        "description": "Represents an IoT device",
        "properties": {
          "device_id": {
            "type": "string",
            "description": "Unique device ID (MAC address).\n\n**Format:** `XX:XX:XX:XX:XX:XX` (hexadecimal separated by colons)\n**Case insensitive**\n",
            "example": "aa:bb:cc:dd:ee:ff"
          },
          "label": {
            "type": "string",
            "description": "Descriptive label for the device.\n\nProvided by the administrator to easily identify the device.\n",
            "example": "Sensore Magico della Temperatura"
          },
          "available_measures": {
            "type": "array",
            "description": "List of available measurements for this device.\n\n**Present only if:** `includeMeasures=true` in the request\n\nEach element represents a unique combination of:\n- Measurement type (`measure`)\n- Sensor type (`sensor_type`)\n- Unit of measure (`unit`)\n",
            "items": {
              "$ref": "#/components/schemas/AvailableMeasure"
            }
          }
        },
        "required": [
          "device_id",
          "label"
        ]
      },
      "AvailableMeasure": {
        "type": "object",
        "description": "Represents an available measurement for a device",
        "properties": {
          "measure": {
            "type": "string",
            "description": "Measurement type.\n\nCan be any value returned by the sensors (e.g., `temperature`, `humidity`, `pressure`, `co2`, `battery_percentage`, `battery_voltage`, `rssi`, `a_current`, `b_current`, `c_current`, `total_current`, `a_voltage`, `b_voltage`, `c_voltage`, `a_act_power`, `b_act_power`, `c_act_power`, `total_act_power`, etc.).\n\nThe value matches exactly what is returned by the sensor's measurement data.\n",
            "example": "temperature"
          },
          "unit": {
            "type": "string",
            "description": "Unit of measure.\n\n**Examples:**\n- `C` or `°C`: Degrees Celsius\n- `%`: Percentage\n- `hPa`: Hectopascal\n- `ppm`: Parts per million\n",
            "example": "C"
          },
          "sensor_type": {
            "type": "string",
            "description": "Sensor type/identifier.\n\n**Usefulness:**\n- Distinguishes between different sensors of the same type\n- Example: two temperature sensors can have different `sensor_type` values\n- Use this value in the `sensorType` parameter to filter\n\n**Format:** Alphanumeric string, may contain hyphens and underscores\n",
            "example": "in100-q1-r-rc0i"
          }
        },
        "required": [
          "measure",
          "unit",
          "sensor_type"
        ]
      },
      "DeviceDataSingle": {
        "type": "object",
        "description": "Single device measurement.\n\nReturned when:\n- You specify `measure` and `sensorType` in `/devices/{deviceId}/last`\n- You specify `measure` and `sensorType` in `/devices/last/bulk` and there's only one result\n- You request historical data with specific filters\n",
        "properties": {
          "timestamp": {
            "type": "string",
            "format": "date-time",
            "description": "Measurement date/time (ISO 8601, UTC)",
            "example": "2026-02-04T15:30:00Z"
          },
          "device_id": {
            "type": "string",
            "description": "Device ID",
            "example": "aa:bb:cc:dd:ee:ff"
          },
          "sensor_type": {
            "type": "string",
            "description": "Sensor type that performed the measurement",
            "example": "in100-q1-r-rc0i"
          },
          "measure": {
            "type": "string",
            "description": "Tipo di misurazione",
            "example": "temperature"
          },
          "unit": {
            "type": "string",
            "description": "Unità di misura",
            "example": "C"
          },
          "value": {
            "oneOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              }
            ],
            "description": "Measurement value.\n\n**Type:** Can be number or string depending on the sensor\n**Precision:** Depends on the sensor and measurement\n",
            "example": 22.5
          }
        },
        "required": [
          "timestamp",
          "device_id",
          "sensor_type",
          "measure",
          "unit",
          "value"
        ]
      },
      "DeviceDataMultiple": {
        "type": "object",
        "description": "Multiple device measurements.\n\nReturned when:\n- You don't specify parameters in `/devices/{deviceId}/last`\n- You specify only `measure` and there are multiple sensors\n- You request bulk data without specific filters\n",
        "properties": {
          "device_id": {
            "type": "string",
            "description": "Device ID",
            "example": "aa:bb:cc:dd:ee:ff"
          },
          "timestamp": {
            "type": "string",
            "format": "date-time",
            "description": "Common timestamp for all measurements.\n\nAll measurements in the `data` array have this same timestamp.\n",
            "example": "2026-02-04T15:30:00Z"
          },
          "data": {
            "type": "array",
            "description": "Array of measurements with the same timestamp",
            "items": {
              "$ref": "#/components/schemas/DeviceDataSingle"
            },
            "minItems": 1
          }
        },
        "required": [
          "device_id",
          "timestamp",
          "data"
        ]
      },
      "BulkRequest": {
        "type": "object",
        "description": "Request to get latest values for multiple devices",
        "properties": {
          "deviceIds": {
            "type": "array",
            "items": {
              "type": "string",
              "pattern": "^[0-9a-fA-F]{2}(:[0-9a-fA-F]{2}){5}$"
            },
            "description": "List of device IDs to query.\n\n**Limitations:**\n- Minimum: 1 device\n- Maximum: 100 devices\n- Format: Array of strings (MAC address)\n",
            "minItems": 1,
            "maxItems": 100,
            "example": [
              "aa:bb:cc:dd:ee:ff",
              "11:22:33:44:55:66"
            ]
          },
          "measure": {
            "type": "string",
            "description": "Filter by measurement type (optional).\n\nIf omitted, returns all measurements for each device.\n",
            "example": "temperature"
          },
          "sensorType": {
            "type": "string",
            "description": "Filter by sensor type (optional).\n\nMust be used together with `measure` to be effective.\n",
            "example": "in100-q1-r-rc0i"
          }
        },
        "required": [
          "deviceIds"
        ]
      },
      "BulkResponse": {
        "type": "object",
        "description": "Response with latest values for multiple devices",
        "properties": {
          "results": {
            "type": "array",
            "description": "Array of results, one per requested device.\n\n**Element format:**\n- If `measure` and `sensorType` specified: single object (`DeviceDataSingle`)\n- Otherwise: object with `device_id`, `timestamp` and `data` array (`DeviceDataMultiple`)\n",
            "items": {
              "oneOf": [
                {
                  "$ref": "#/components/schemas/DeviceDataSingle"
                },
                {
                  "$ref": "#/components/schemas/DeviceDataMultiple"
                }
              ]
            }
          },
          "count": {
            "type": "integer",
            "description": "Number of results returned.\n\nMay be less than `requested` if some devices don't have available data.\n",
            "example": 2
          },
          "requested": {
            "type": "integer",
            "description": "Number of devices requested in the request",
            "example": 2
          }
        },
        "required": [
          "results",
          "count",
          "requested"
        ]
      },
      "HistoryResponse": {
        "type": "object",
        "description": "Response with historical data for a device",
        "properties": {
          "data": {
            "type": "array",
            "description": "Array of historical data.\n\n**Element format:**\n- If `granularity=raw`: `DeviceDataSingle` objects\n- If `granularity` different from `raw`: `DeviceDataAggregated` objects\n",
            "items": {
              "oneOf": [
                {
                  "$ref": "#/components/schemas/DeviceDataSingle"
                },
                {
                  "$ref": "#/components/schemas/DeviceDataAggregated"
                }
              ]
            }
          },
          "pagination": {
            "$ref": "#/components/schemas/Pagination"
          },
          "granularity": {
            "type": "string",
            "description": "Granularity of returned data",
            "enum": [
              "raw",
              "15m",
              "1h",
              "1d"
            ],
            "example": "raw"
          }
        },
        "required": [
          "data",
          "pagination",
          "granularity"
        ]
      },
      "DeviceDataAggregated": {
        "type": "object",
        "description": "Aggregated data (when granularity is not \"raw\").\n\nContains aggregated statistics for the specified time interval.\n",
        "properties": {
          "timestamp": {
            "type": "string",
            "format": "date-time",
            "description": "Timestamp of the aggregation interval.\n\nFor `15m`: start of the 15-minute period\nFor `1h`: start of the hour\nFor `1d`: start of the day (00:00:00 UTC)\n",
            "example": "2026-02-04T00:00:00Z"
          },
          "device_id": {
            "type": "string",
            "description": "Device ID",
            "example": "aa:bb:cc:dd:ee:ff"
          },
          "sensor_type": {
            "type": "string",
            "description": "Sensor type",
            "example": "in100-q1-r-rc0i"
          },
          "measure": {
            "type": "string",
            "description": "Tipo di misurazione",
            "example": "temperature"
          },
          "unit": {
            "type": "string",
            "description": "Unità di misura",
            "example": "C"
          },
          "avg_value": {
            "type": "number",
            "description": "Average value in the aggregation interval.\n\nCalculated as the arithmetic mean of all values in the interval.\n",
            "example": 22.3
          },
          "max_value": {
            "type": "number",
            "description": "Maximum value in the aggregation interval.\n",
            "example": 24.1
          },
          "min_value": {
            "type": "number",
            "description": "Minimum value in the aggregation interval.\n",
            "example": 20.5
          }
        },
        "required": [
          "timestamp",
          "device_id",
          "sensor_type",
          "measure",
          "unit",
          "avg_value",
          "max_value",
          "min_value"
        ]
      },
      "Pagination": {
        "type": "object",
        "description": "Pagination information",
        "properties": {
          "limit": {
            "type": "integer",
            "description": "Maximum number of results requested",
            "example": 100
          },
          "offset": {
            "type": "integer",
            "description": "Number of results skipped",
            "example": 0
          },
          "total": {
            "type": "integer",
            "description": "Total number of available results.\n\nRegardless of `limit` and `offset`.\n",
            "example": 96
          },
          "hasMore": {
            "type": "boolean",
            "description": "Indicates if there are more results available.\n\n`true` if `offset + returned < total`\n",
            "example": true
          },
          "returned": {
            "type": "integer",
            "description": "Number of results actually returned in this response",
            "example": 100
          }
        },
        "required": [
          "limit",
          "offset",
          "total",
          "hasMore",
          "returned"
        ]
      },
      "Error": {
        "type": "object",
        "description": "Standard error object",
        "properties": {
          "error": {
            "type": "string",
            "description": "Error code.\n\n**Common values:**\n- `Bad Request`: Malformed request\n- `Unauthorized`: Authentication failed\n- `Forbidden`: Insufficient permissions\n- `Not Found`: Resource not found\n- `Internal Server Error`: Server error\n- `TOO_MANY_DEVICE_IDS`: Too many devices in bulk request\n",
            "example": "Bad Request"
          },
          "message": {
            "type": "string",
            "description": "Descriptive error message.\n\nProvides details on what went wrong and how to resolve it.\n",
            "example": "Missing required query parameters: startDate and endDate"
          }
        },
        "required": [
          "error",
          "message"
        ]
      },
      "TagScope": {
        "type": "object",
        "description": "Scope of a tag or alarm definition.",
        "properties": {
          "level": {
            "type": "string",
            "enum": [
              "global",
              "tenant",
              "site"
            ]
          },
          "tenantId": {
            "type": "integer",
            "description": "Required for `tenant` and `site` levels; absent/null for `global`."
          },
          "siteId": {
            "type": "integer",
            "description": "Required for `site` level."
          }
        },
        "required": [
          "level"
        ]
      },
      "Tag": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "label": {
            "type": "string"
          },
          "scope": {
            "$ref": "#/components/schemas/TagScope"
          },
          "createdBy": {
            "type": "string",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedBy": {
            "type": "string",
            "nullable": true
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "label",
          "scope",
          "createdAt",
          "updatedAt"
        ]
      },
      "TagDeletedResponse": {
        "type": "object",
        "properties": {
          "deleted": {
            "type": "string",
            "format": "uuid",
            "description": "Id of the deleted tag."
          }
        },
        "required": [
          "deleted"
        ]
      },
      "TagDevicesListResponse": {
        "type": "object",
        "properties": {
          "devices": {
            "type": "array",
            "description": "Device IDs (MAC addresses) associated with the tag.",
            "items": {
              "type": "string"
            }
          },
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer"
          }
        },
        "required": [
          "devices",
          "limit",
          "offset"
        ]
      },
      "AlarmDefinition": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "origin": {
            "type": "string",
            "enum": [
              "system",
              "user"
            ]
          },
          "label": {
            "type": "string"
          },
          "conditionType": {
            "type": "string",
            "enum": [
              "non_reporting",
              "threshold"
            ]
          },
          "conditionConfiguration": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/NonReportingConfiguration"
              },
              {
                "$ref": "#/components/schemas/ThresholdConfiguration"
              }
            ]
          },
          "targetDevices": {
            "$ref": "#/components/schemas/AlarmTarget"
          },
          "scope": {
            "$ref": "#/components/schemas/TagScope"
          },
          "enabled": {
            "type": "boolean"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "origin",
          "label",
          "conditionType",
          "conditionConfiguration",
          "targetDevices",
          "scope",
          "enabled",
          "createdAt",
          "updatedAt"
        ]
      },
      "AlarmDefinitionsListResponse": {
        "type": "object",
        "properties": {
          "definitions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AlarmDefinition"
            }
          }
        },
        "required": [
          "definitions"
        ]
      },
      "AlarmDefinitionDeletedResponse": {
        "type": "object",
        "properties": {
          "deleted": {
            "type": "string",
            "format": "uuid"
          }
        },
        "required": [
          "deleted"
        ]
      },
      "AlarmInstance": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "definitionId": {
            "type": "string",
            "format": "uuid"
          },
          "definitionLabel": {
            "type": "string"
          },
          "conditionType": {
            "type": "string",
            "enum": [
              "non_reporting",
              "threshold"
            ]
          },
          "deviceId": {
            "type": "string"
          },
          "deviceLabel": {
            "type": "string",
            "nullable": true
          },
          "tenantId": {
            "type": "integer"
          },
          "siteId": {
            "type": "integer"
          },
          "state": {
            "type": "string",
            "enum": [
              "active",
              "acknowledged"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "definitionId",
          "definitionLabel",
          "conditionType",
          "deviceId",
          "tenantId",
          "siteId",
          "state",
          "createdAt",
          "updatedAt"
        ]
      },
      "AlarmInstancesListResponse": {
        "type": "object",
        "properties": {
          "instances": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AlarmInstance"
            }
          }
        },
        "required": [
          "instances"
        ]
      },
      "AlarmHistoryEntry": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "instanceId": {
            "type": "string",
            "format": "uuid"
          },
          "definitionId": {
            "type": "string",
            "format": "uuid"
          },
          "definitionLabel": {
            "type": "string"
          },
          "conditionType": {
            "type": "string"
          },
          "deviceId": {
            "type": "string"
          },
          "tenantId": {
            "type": "integer"
          },
          "siteId": {
            "type": "integer"
          },
          "state": {
            "type": "string",
            "description": "Transition state (includes closing states `resolved` / `cancelled`)."
          },
          "conditionConfigurationSnapshot": {
            "type": "object",
            "additionalProperties": true
          },
          "transitionedAt": {
            "type": "string",
            "format": "date-time"
          },
          "actor": {
            "type": "string",
            "nullable": true
          },
          "note": {
            "type": "string",
            "nullable": true
          }
        },
        "required": [
          "id",
          "instanceId",
          "definitionId",
          "definitionLabel",
          "conditionType",
          "deviceId",
          "tenantId",
          "siteId",
          "state",
          "transitionedAt"
        ]
      },
      "AlarmHistoryListResponse": {
        "type": "object",
        "properties": {
          "history": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AlarmHistoryEntry"
            }
          },
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer"
          }
        },
        "required": [
          "history",
          "limit",
          "offset"
        ]
      },
      "DissociateResponse": {
        "type": "object",
        "properties": {
          "dissociated": {
            "type": "integer",
            "description": "Number of associations removed."
          }
        },
        "required": [
          "dissociated"
        ]
      },
      "DeviceTagsResponse": {
        "type": "object",
        "properties": {
          "tags": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Tag"
            }
          }
        },
        "required": [
          "tags"
        ]
      },
      "TagResponse": {
        "type": "object",
        "properties": {
          "tag": {
            "$ref": "#/components/schemas/Tag"
          }
        },
        "required": [
          "tag"
        ]
      },
      "PatchInstanceRequest": {
        "type": "object",
        "properties": {
          "action": {
            "type": "string",
            "enum": [
              "acknowledge",
              "resolve"
            ]
          },
          "note": {
            "type": "string",
            "nullable": true
          }
        },
        "required": [
          "action"
        ]
      },
      "AlarmInstanceResponse": {
        "type": "object",
        "properties": {
          "instance": {
            "allOf": [
              {
                "$ref": "#/components/schemas/AlarmInstance"
              }
            ],
            "nullable": true
          }
        },
        "required": [
          "instance"
        ]
      },
      "AlarmDefinitionResponse": {
        "type": "object",
        "properties": {
          "definition": {
            "$ref": "#/components/schemas/AlarmDefinition"
          }
        },
        "required": [
          "definition"
        ]
      },
      "TagListResponse": {
        "type": "object",
        "properties": {
          "tags": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Tag"
            }
          },
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer"
          }
        },
        "required": [
          "tags",
          "limit",
          "offset"
        ]
      },
      "AssociateTagsResponse": {
        "type": "object",
        "properties": {
          "associated": {
            "type": "integer"
          },
          "tagIds": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          }
        },
        "required": [
          "associated",
          "tagIds"
        ]
      },
      "TagDeviceCountResponse": {
        "type": "object",
        "properties": {
          "count": {
            "type": "integer"
          }
        },
        "required": [
          "count"
        ]
      },
      "DeviceAlarmsResponse": {
        "type": "object",
        "properties": {
          "deviceId": {
            "type": "string"
          },
          "definitions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AlarmDefinition"
            }
          }
        },
        "required": [
          "deviceId",
          "definitions"
        ]
      },
      "DeviceRuleRequest": {
        "type": "object",
        "properties": {
          "deviceId": {
            "type": "string",
            "minLength": 1
          },
          "mode": {
            "type": "string",
            "enum": [
              "include",
              "exclude"
            ]
          }
        },
        "required": [
          "deviceId",
          "mode"
        ]
      },
      "PreviewDevicesResponse": {
        "type": "object",
        "properties": {
          "definitionId": {
            "type": "string",
            "format": "uuid"
          },
          "devices": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PreviewDevice"
            }
          }
        },
        "required": [
          "definitionId",
          "devices"
        ]
      },
      "UpdateTagRequest": {
        "type": "object",
        "properties": {
          "label": {
            "type": "string",
            "minLength": 1
          }
        },
        "required": [
          "label"
        ]
      },
      "UpdateAlarmDefinitionRequest": {
        "type": "object",
        "description": "At least one field must be provided. `conditionType` and `scope` are immutable.",
        "properties": {
          "label": {
            "type": "string",
            "minLength": 1
          },
          "conditionConfiguration": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/NonReportingConfiguration"
              },
              {
                "$ref": "#/components/schemas/ThresholdConfiguration"
              }
            ]
          },
          "targetDevices": {
            "$ref": "#/components/schemas/AlarmTarget"
          },
          "enabled": {
            "type": "boolean"
          }
        }
      },
      "DeviceRuleResponse": {
        "type": "object",
        "properties": {
          "definitionId": {
            "type": "string",
            "format": "uuid"
          },
          "deviceId": {
            "type": "string"
          },
          "mode": {
            "type": "string",
            "enum": [
              "include",
              "exclude"
            ]
          }
        },
        "required": [
          "definitionId",
          "deviceId",
          "mode"
        ]
      },
      "AssociateDevicesResponse": {
        "type": "object",
        "properties": {
          "associated": {
            "type": "integer",
            "description": "Number of new associations created (already-associated devices are skipped)."
          },
          "deviceIds": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "associated",
          "deviceIds"
        ]
      },
      "DeviceRuleDeletedResponse": {
        "type": "object",
        "properties": {
          "definitionId": {
            "type": "string",
            "format": "uuid"
          },
          "deviceId": {
            "type": "string"
          },
          "removed": {
            "type": "boolean",
            "description": "`true` if a pin was removed; `false` if none existed (idempotent)."
          }
        },
        "required": [
          "definitionId",
          "deviceId",
          "removed"
        ]
      },
      "CreateTagRequest": {
        "type": "object",
        "properties": {
          "label": {
            "type": "string",
            "minLength": 1
          },
          "scope": {
            "$ref": "#/components/schemas/TagScope"
          }
        },
        "required": [
          "label",
          "scope"
        ]
      },
      "DeviceIdsRequest": {
        "type": "object",
        "properties": {
          "deviceIds": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "minItems": 1
          }
        },
        "required": [
          "deviceIds"
        ]
      },
      "TagIdsRequest": {
        "type": "object",
        "properties": {
          "tagIds": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "minItems": 1
          }
        },
        "required": [
          "tagIds"
        ]
      },
      "CreateAlarmDefinitionRequest": {
        "type": "object",
        "properties": {
          "label": {
            "type": "string",
            "minLength": 1
          },
          "conditionType": {
            "type": "string",
            "enum": [
              "non_reporting",
              "threshold"
            ]
          },
          "conditionConfiguration": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/NonReportingConfiguration"
              },
              {
                "$ref": "#/components/schemas/ThresholdConfiguration"
              }
            ]
          },
          "targetDevices": {
            "$ref": "#/components/schemas/AlarmTarget"
          },
          "enabled": {
            "type": "boolean",
            "default": true
          },
          "scope": {
            "$ref": "#/components/schemas/TagScope"
          },
          "origin": {
            "type": "string",
            "enum": [
              "system",
              "user"
            ],
            "description": "Defaults to `system` for GLOBAL callers, `user` otherwise. Only GLOBAL may set `system`."
          }
        },
        "required": [
          "label",
          "conditionType",
          "conditionConfiguration",
          "scope"
        ]
      },
      "AlarmTarget": {
        "type": "object",
        "description": "Device selector. Either explicit (`deviceIds`) OR rule-based (`siteIds` / `deviceTypes` / `tags`) — the two\nmodes are mutually exclusive. An empty object `{}` targets all devices in scope. Arrays must be non-empty.\n",
        "properties": {
          "deviceIds": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "siteIds": {
            "type": "array",
            "items": {
              "type": "integer"
            }
          },
          "deviceTypes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          }
        }
      },
      "ThresholdConfiguration": {
        "type": "object",
        "description": "Config for `conditionType: threshold` — a measure series over/under a value for a fraction of a window.",
        "properties": {
          "series": {
            "type": "string",
            "description": "Logical measure to watch (e.g. `temperature`); the concrete sensor_type is resolved per device."
          },
          "threshold": {
            "type": "number"
          },
          "direction": {
            "type": "string",
            "enum": [
              "over",
              "under"
            ],
            "description": "`over` = breached above the threshold; `under` = breached below."
          },
          "tolerance": {
            "type": "number",
            "minimum": 0,
            "description": "Absolute margin around the threshold (same unit as the measure)."
          },
          "durationMs": {
            "type": "integer",
            "description": "Width of the evaluation window in milliseconds.",
            "exclusiveMinimum": 0
          },
          "percentage": {
            "type": "number",
            "description": "Fraction (0,1] of samples in the window that must be out of range to breach.",
            "exclusiveMinimum": 0,
            "maximum": 1
          }
        },
        "required": [
          "series",
          "threshold",
          "direction",
          "tolerance",
          "durationMs",
          "percentage"
        ]
      },
      "NonReportingConfiguration": {
        "type": "object",
        "description": "Config for `conditionType: non_reporting` — device silent for `thresholdMultiplier` × its sampling interval.",
        "properties": {
          "thresholdMultiplier": {
            "type": "number",
            "description": "Multiplier applied to the device sampling interval before an alarm is raised.",
            "exclusiveMinimum": 0
          }
        },
        "required": [
          "thresholdMultiplier"
        ]
      },
      "PreviewDevice": {
        "type": "object",
        "properties": {
          "deviceId": {
            "type": "string"
          },
          "deviceType": {
            "type": "string"
          },
          "source": {
            "type": "string",
            "enum": [
              "pg",
              "shelly"
            ],
            "description": "Where the device state is read from (`pg` = telemetry table, `shelly` = actuator cache)."
          }
        },
        "required": [
          "deviceId",
          "deviceType",
          "source"
        ]
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Invalid request.\n\n**Common causes:**\n- Missing or malformed parameters\n- Values out of range\n- Invalid date format\n- Empty or too large array\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "examples": {
              "parametri_mancanti": {
                "summary": "Missing required parameters",
                "value": {
                  "error": "Bad Request",
                  "message": "Missing required query parameters: startDate and endDate"
                }
              },
              "formato_invalido": {
                "summary": "Invalid format",
                "value": {
                  "error": "Bad Request",
                  "message": "Invalid JSON in request body"
                }
              },
              "limite_superato": {
                "summary": "Limit exceeded",
                "value": {
                  "error": "TOO_MANY_DEVICE_IDS",
                  "message": "Max 100 deviceIds per request."
                }
              }
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Unauthorized - authentication failed.\n\n**Causes:**\n- Missing API key in `x-api-key` header\n- Invalid or expired API key\n- API key not associated with a valid Usage Plan\n\n**Solution:**\n- Verify you include the `x-api-key` header in all requests\n- Check that the API key is correct\n- Contact support if the problem persists\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Unauthorized",
              "message": "API Key not found or invalid auth type"
            }
          }
        }
      },
      "Forbidden": {
        "description": "Access denied - insufficient permissions.\n\n**Causes:**\n- Attempt to access unauthorized resources\n- Insufficient permissions for the requested resource\n\n**Solution:**\n- Verify you have the necessary permissions\n- Contact support if you believe you should have access to the resource\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "examples": {
              "accesso_negato": {
                "summary": "Access denied",
                "value": {
                  "error": "Forbidden",
                  "message": "Access denied to this resource"
                }
              },
              "permessi_insufficienti": {
                "summary": "Insufficient permissions",
                "value": {
                  "error": "Forbidden",
                  "message": "Missing permission: devices:read"
                }
              }
            }
          }
        }
      },
      "NotFound": {
        "description": "Resource not found.\n\n**Causes:**\n- Non-existent Device ID\n- Device not accessible with your API key\n- No data available for the requested device\n\n**Note:** If the device exists but has no data, you will still receive 404.\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Not Found",
              "message": "No data found for device aa:bb:cc:dd:ee:ff or device not accessible"
            }
          }
        }
      },
      "InternalServerError": {
        "description": "Internal server error.\n\n**What to do:**\n- Retry the request after a few seconds\n- If the problem persists, contact support\n- Provide the `requestId` if available in logs\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Internal Server Error",
              "message": "An unexpected error occurred"
            }
          }
        }
      },
      "UnprocessableEntity": {
        "description": "The request is well-formed but semantically invalid.\n\n**Causes:**\n- Device is not an actuator\n- `action` is not valid for the device's component type (e.g. `stop` on a switch)\n- `to_position` action missing or invalid `position` (must be 0–100)\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Unprocessable Entity",
              "message": "Action 'stop' is not valid for component type 'switch'"
            }
          }
        }
      },
      "Conflict": {
        "description": "Conflict — usually optimistic locking or concurrent update.\n\n**Typical causes:**\n- Wrong or outdated `version` on PATCH or toggle\n- Rule not on the requested device\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Conflict",
              "message": "Stale or missing rule version, or rule not on this device"
            }
          }
        }
      },
      "TooManyRequests": {
        "description": "Too many requests — rate limiting or quota exceeded.\n\nUsed for daily high-frequency file download quotas (`QuotaExceeded` body shape).\n",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "error": {
                  "type": "string",
                  "example": "QuotaExceeded"
                },
                "message": {
                  "type": "string",
                  "example": "Daily download limit reached. Limit: 100, Used: 100"
                },
                "quota": {
                  "type": "object",
                  "properties": {
                    "dailyLimit": {
                      "type": "integer",
                      "example": 100
                    },
                    "usedToday": {
                      "type": "integer",
                      "example": 100
                    },
                    "remaining": {
                      "type": "integer",
                      "example": 0
                    },
                    "resetAt": {
                      "type": "string",
                      "format": "date-time",
                      "example": "2026-02-20T00:00:00.000Z"
                    }
                  }
                }
              }
            },
            "example": {
              "error": "QuotaExceeded",
              "message": "Daily download limit reached. Limit: 100, Used: 100",
              "quota": {
                "dailyLimit": 100,
                "usedToday": 100,
                "remaining": 0,
                "resetAt": "2026-02-20T00:00:00.000Z"
              }
            }
          }
        }
      },
      "RejectedAssociation": {
        "description": "One or more items were outside the caller scope or not applicable to the tag/device.\nThe operation is **all-or-nothing**: no changes were made and the offending items are listed in `rejected`.\n",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "error": {
                  "type": "string",
                  "example": "BadRequest"
                },
                "message": {
                  "type": "string",
                  "example": "Some items are outside the caller scope or not applicable to the tag; no changes were made"
                },
                "rejected": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "additionalProperties": true
                  }
                }
              },
              "required": [
                "error",
                "message",
                "rejected"
              ]
            }
          }
        }
      }
    },
    "parameters": {
      "TagId": {
        "name": "tagId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "AlarmDefinitionId": {
        "name": "definitionId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "AlarmInstanceId": {
        "name": "instanceId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "DeviceIdPath": {
        "name": "deviceId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        },
        "description": "Device MAC-style identifier (URL-encode colons when needed)."
      },
      "Limit": {
        "name": "limit",
        "in": "query",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 500,
          "default": 100
        }
      },
      "Offset": {
        "name": "offset",
        "in": "query",
        "schema": {
          "type": "integer",
          "minimum": 0,
          "default": 0
        }
      }
    }
  }
}
