{
  "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",
    "version": "1.0.0",
    "contact": {
      "name": "EOS Support Team",
      "email": "pietro.parini@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\n- Query available measurements for each device\n"
    },
    {
      "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\n"
    },
    {
      "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\n"
    }
  ],
  "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",
        "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
            }
          }
        ],
        "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"
          }
        }
      }
    }
  },
  "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"
      }
    },
    "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"
        ]
      }
    },
    "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"
            }
          }
        }
      }
    }
  }
}