{
  "openapi": "3.0.1",
  "servers": [{ "url": "https://apis.<domain>" }],
  "info": { "title": "connhex-things", "version": "1.5.0" },
  "paths": {
    "/iot/things": {
      "get": {
        "summary": "List things",
        "description": "Returns a paginated list of things. Use limit and offset to iterate through results.\n\nRequired Permission:\n\n - Resource: ```core:things```\n\n - Action: ```core:things:list```",
        "tags": ["Things"],
        "parameters": [
          { "$ref": "#/components/parameters/Limit" },
          { "$ref": "#/components/parameters/Offset" },
          { "$ref": "#/components/parameters/Name" },
          { "$ref": "#/components/parameters/Order" },
          { "$ref": "#/components/parameters/Direction" },
          { "$ref": "#/components/parameters/Metadata" }
        ],
        "responses": {
          "200": { "$ref": "#/components/responses/ThingsPageRes" },
          "400": { "description": "Failed due to malformed query parameters." },
          "401": { "description": "Unauthorized." },
          "404": { "description": "Referenced entity does not exist." },
          "422": { "description": "Unprocessable entity." },
          "500": { "$ref": "#/components/responses/ServiceError" }
        }
      }
    },
    "/iot/things/status": {
      "post": {
        "summary": "Batch fetch thing connectivity statuses",
        "description": "Returns connectivity statuses for the given thing IDs. IDs that do not exist or are not accessible are omitted from the response. Things that have never connected are also omitted.\n\nRequired Permission:\n\n - Resource: ```core:things:{id}``` (each)\n\n - Action: ```core:things:get```",
        "tags": ["Things"],
        "requestBody": { "$ref": "#/components/requestBodies/BatchStatusReq" },
        "responses": {
          "200": { "$ref": "#/components/responses/BatchStatusRes" },
          "400": {
            "description": "Failed due to malformed JSON or empty IDs list."
          },
          "401": { "description": "Unauthorized." },
          "415": { "description": "Missing or unsupported content type." },
          "500": { "$ref": "#/components/responses/ServiceError" }
        }
      }
    },
    "/iot/things/status/summary": {
      "get": {
        "summary": "Fleet status summary",
        "description": "Returns aggregated connectivity counts across the fleet: online, offline, never connected, and recently active devices.\n\nRequired Permission:\n\n - Resource: ```core:things```\n\n - Action: ```core:things:statusSummary```",
        "tags": ["Things"],
        "responses": {
          "200": {
            "description": "Fleet status summary.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/StatusSummary" }
              }
            }
          },
          "401": { "description": "Unauthorized." },
          "403": { "description": "Insufficient permissions." },
          "500": { "$ref": "#/components/responses/ServiceError" }
        }
      }
    },
    "/iot/things/status/flapping": {
      "get": {
        "summary": "Devices with excessive reconnections",
        "description": "Returns devices that have reconnected more than a threshold number of times within a time window, ordered by reconnect count descending.\n\nRequired Permission:\n\n - Resource: ```core:things:{id}``` (each)\n\n - Action: ```core:things:get```",
        "tags": ["Things"],
        "parameters": [
          {
            "name": "window",
            "description": "Time window to check for reconnections (e.g. \"1h\", \"30m\").",
            "in": "query",
            "schema": { "type": "string", "default": "1h" }
          },
          {
            "name": "min",
            "description": "Minimum reconnect count to include.",
            "in": "query",
            "schema": { "type": "integer", "default": 10 }
          },
          {
            "name": "limit",
            "description": "Maximum number of results.",
            "in": "query",
            "schema": { "type": "integer", "default": 100 }
          }
        ],
        "responses": {
          "200": {
            "description": "Flapping devices list.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/FlappingResponse" }
              }
            }
          },
          "400": { "description": "Invalid query parameters." },
          "401": { "description": "Unauthorized." },
          "500": { "$ref": "#/components/responses/ServiceError" }
        }
      }
    },
    "/iot/things/{thingId}": {
      "get": {
        "summary": "Retrieves thing info",
        "description": "Retrieves thing info.\n\nRequired Permission:\n\n - Resource: ```core:things:{id}```\n\n - Action: ```core:things:get```",
        "tags": ["Things"],
        "parameters": [{ "$ref": "#/components/parameters/ThingId" }],
        "responses": {
          "200": { "$ref": "#/components/responses/ThingRes" },
          "401": { "description": "Unauthorized." },
          "404": { "description": "Thing not found." },
          "422": { "description": "Unprocessable entity." },
          "500": { "$ref": "#/components/responses/ServiceError" }
        }
      },
      "put": {
        "summary": "Updates thing info",
        "description": "Update is performed by replacing the current resource data with values provided in a request payload. Note that the thing's type and ID cannot be changed.\n\nRequired Permission:\n\n - Resource: ```core:things:{id}```\n\n - Action: ```core:things:update```",
        "tags": ["Things"],
        "parameters": [{ "$ref": "#/components/parameters/ThingId" }],
        "requestBody": { "$ref": "#/components/requestBodies/ThingUpdateReq" },
        "responses": {
          "200": { "description": "Thing updated." },
          "400": { "description": "Bad request." },
          "401": { "description": "Unauthorized." },
          "404": { "description": "Thing not found." },
          "415": { "description": "Missing or unsupported content type." },
          "500": { "$ref": "#/components/responses/ServiceError" }
        }
      }
    },
    "/iot/things/{thingId}/uptime": {
      "get": {
        "summary": "Device uptime in a time range",
        "description": "Returns total connected seconds for a device within the specified time range, along with the individual connect/disconnect events for session timeline visualization.\n\nRequired Permission:\n\n - Resource: ```core:things:{id}```\n\n - Action: ```core:things:get```",
        "tags": ["Things"],
        "parameters": [
          { "$ref": "#/components/parameters/ThingId" },
          {
            "name": "from",
            "description": "Start of time range (unix timestamp).",
            "in": "query",
            "required": true,
            "schema": { "type": "integer", "format": "int64" }
          },
          {
            "name": "to",
            "description": "End of time range (unix timestamp).",
            "in": "query",
            "required": true,
            "schema": { "type": "integer", "format": "int64" }
          }
        ],
        "responses": {
          "200": {
            "description": "Uptime result.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/UptimeResponse" }
              }
            }
          },
          "400": { "description": "Invalid query parameters." },
          "401": { "description": "Unauthorized." },
          "500": { "$ref": "#/components/responses/ServiceError" }
        }
      }
    },
    "/iot/things/{thingId}/channels": {
      "get": {
        "summary": "List of channels connected to a specified thing",
        "description": "Retrieves list of channels connected to a specified thing.\n\nRequired Permission:\n\n - Resource: ```core:things:{id}```\n\n - Action: ```core:things:getChannels```",
        "tags": ["Things"],
        "parameters": [
          { "$ref": "#/components/parameters/ThingId" },
          { "$ref": "#/components/parameters/Offset" },
          { "$ref": "#/components/parameters/Limit" },
          { "$ref": "#/components/parameters/Connected" }
        ],
        "responses": {
          "200": { "$ref": "#/components/responses/ChannelsPageRes" },
          "400": { "description": "Bad request." },
          "401": { "description": "Unauthorized." },
          "404": { "description": "Thing not found." },
          "422": { "description": "Unprocessable entity." },
          "500": { "$ref": "#/components/responses/ServiceError" }
        }
      }
    },
    "/iot/channels/{chanId}": {
      "get": {
        "summary": "Retrieves channel info",
        "description": "Retrieves channel info.\n\nRequired Permission:\n\n - Resource: ```core:channels:{chanId}```\n\n - Action: ```core:channels:get```",
        "tags": ["Channels"],
        "parameters": [{ "$ref": "#/components/parameters/ChanId" }],
        "responses": {
          "200": { "$ref": "#/components/responses/ChannelRes" },
          "400": { "description": "Bad request." },
          "401": { "description": "Unauthorized." },
          "404": { "description": "Channel not found." },
          "422": { "description": "Unprocessable entity." },
          "500": { "$ref": "#/components/responses/ServiceError" }
        }
      },
      "put": {
        "summary": "Updates channel info",
        "description": "Update is performed by replacing the current resource data with values provided in a request payload. Note that the channel's ID will not be affected.\n\nRequired Permission:\n\n - Resource: ```core:channels:{chanId}```\n\n - Action: ```core:channels:update```",
        "tags": ["Channels"],
        "parameters": [{ "$ref": "#/components/parameters/ChanId" }],
        "requestBody": {
          "$ref": "#/components/requestBodies/ChannelCreateReq"
        },
        "responses": {
          "200": { "description": "Channel updated." },
          "400": { "description": "Bad request." },
          "401": { "description": "Unauthorized." },
          "404": { "description": "Channel not found." },
          "415": { "description": "Missing or unsupported content type." },
          "500": { "$ref": "#/components/responses/ServiceError" }
        }
      }
    },
    "/iot/models": {
      "post": {
        "summary": "Creates new model",
        "description": "Creates new model. User identified by the provided access token will be the model's owner.\n\nRequired Permission:\n\n - Resource: ```core:models```\n\n - Action: ```core:models:create```",
        "tags": ["Models"],
        "requestBody": { "$ref": "#/components/requestBodies/ModelCreateReq" },
        "responses": {
          "201": { "$ref": "#/components/responses/ModelCreateRes" },
          "400": { "description": "Failed due to malformed JSON." },
          "401": { "description": "Missing or invalid access token provided." },
          "409": { "description": "Entity already exist." },
          "415": { "description": "Missing or invalid content type." },
          "500": { "$ref": "#/components/responses/ServiceError" }
        }
      },
      "get": {
        "summary": "Retrieves models",
        "description": "Retrieves a list of models. Due to performance concerns, data is retrieved in subsets. The API must ensure that the entire dataset is consumed either by making subsequent requests, or by increasing the subset size of the initial request.\n\nRequired Permission:\n\n - Resource: ```core:models```\n\n - Action: ```core:models:list```",
        "tags": ["Models"],
        "parameters": [
          { "$ref": "#/components/parameters/Limit" },
          { "$ref": "#/components/parameters/Offset" },
          { "$ref": "#/components/parameters/Name" },
          { "$ref": "#/components/parameters/Order" },
          { "$ref": "#/components/parameters/Direction" },
          { "$ref": "#/components/parameters/Metadata" },
          { "$ref": "#/components/parameters/Tag" },
          { "$ref": "#/components/parameters/Tenant" }
        ],
        "responses": {
          "200": { "$ref": "#/components/responses/ModelsPageRes" },
          "400": { "description": "Failed due to malformed query parameters." },
          "401": { "description": "Missing or invalid access token provided." },
          "422": { "description": "Database can't process request." },
          "500": { "$ref": "#/components/responses/ServiceError" }
        }
      }
    },
    "/iot/models/{modelId}": {
      "get": {
        "summary": "Retrieves model info",
        "description": "Retrieves model info.\n\nRequired Permission:\n\n - Resource: ```core:models:{modelId}```\n\n - Action: ```core:models:get```",
        "tags": ["Models"],
        "parameters": [{ "$ref": "#/components/parameters/ModelId" }],
        "responses": {
          "200": { "$ref": "#/components/responses/ModelRes" },
          "400": { "description": "Failed due to malformed model ID." },
          "401": { "description": "Missing or invalid access token provided." },
          "404": { "description": "Model does not exist." },
          "500": { "$ref": "#/components/responses/ServiceError" }
        }
      },
      "put": {
        "summary": "Updates model info",
        "description": "Updates model's name, description, and metadata.\n\nRequired Permission:\n\n - Resource: ```core:models:{modelId}```\n\n - Action: ```core:models:update```",
        "tags": ["Models"],
        "parameters": [{ "$ref": "#/components/parameters/ModelId" }],
        "requestBody": { "$ref": "#/components/requestBodies/ModelUpdateReq" },
        "responses": {
          "200": { "$ref": "#/components/responses/ModelRes" },
          "400": { "description": "Failed due to malformed JSON." },
          "401": { "description": "Missing or invalid access token provided." },
          "404": { "description": "Model does not exist." },
          "415": { "description": "Missing or invalid content type." },
          "500": { "$ref": "#/components/responses/ServiceError" }
        }
      },
      "delete": {
        "summary": "Removes a model",
        "description": "Removes a model.\n\nRequired Permission:\n\n - Resource: ```core:models:{modelId}```\n\n - Action: ```core:models:delete```",
        "tags": ["Models"],
        "parameters": [{ "$ref": "#/components/parameters/ModelId" }],
        "responses": {
          "204": { "description": "Model removed." },
          "400": { "description": "Failed due to malformed model ID." },
          "401": { "description": "Missing or invalid access token provided." },
          "500": { "$ref": "#/components/responses/ServiceError" }
        }
      }
    },
    "/iot/models/{modelId}/tags": {
      "put": {
        "summary": "Updates model tags",
        "description": "Update model's tags.\n\nRequired Permission:\n\n - Resource: ```core:models:{modelId}```\n\n - Action: ```core:models:update```",
        "tags": ["Models"],
        "parameters": [{ "$ref": "#/components/parameters/ModelId" }],
        "requestBody": { "$ref": "#/components/requestBodies/ModelTagsReq" },
        "responses": {
          "200": { "$ref": "#/components/responses/ModelRes" },
          "400": { "description": "Failed due to malformed JSON." },
          "401": { "description": "Missing or invalid access token provided." },
          "404": { "description": "Model does not exist." },
          "415": { "description": "Missing or invalid content type." },
          "500": { "$ref": "#/components/responses/ServiceError" }
        }
      }
    },
    "/iot/models/{modelId}/tenants": {
      "put": {
        "summary": "Updates model tenants",
        "description": "Update model's tenant list.\n\nRequired Permission:\n\n - Resource: ```core:models:{modelId}```\n\n - Action: ```core:models:assignTenants```",
        "tags": ["Models"],
        "parameters": [{ "$ref": "#/components/parameters/ModelId" }],
        "requestBody": { "$ref": "#/components/requestBodies/ModelTenantsReq" },
        "responses": {
          "200": { "$ref": "#/components/responses/ModelRes" },
          "400": { "description": "Failed due to malformed JSON." },
          "401": { "description": "Missing or invalid access token provided." },
          "404": { "description": "Model does not exist." },
          "415": { "description": "Missing or invalid content type." },
          "500": { "$ref": "#/components/responses/ServiceError" }
        }
      }
    },
    "/iot/models/{modelId}/things": {
      "get": {
        "summary": "List things by model",
        "description": "Retrieves list of things belonging to specified model with pagination metadata.\n\nRequired Permission:\n\n - Resource: ```core:models:{modelId}```\n\n - Action: ```core:models:getThings```",
        "tags": ["Models"],
        "parameters": [
          { "$ref": "#/components/parameters/ModelId" },
          { "$ref": "#/components/parameters/Offset" },
          { "$ref": "#/components/parameters/Limit" },
          { "$ref": "#/components/parameters/Order" },
          { "$ref": "#/components/parameters/Direction" }
        ],
        "responses": {
          "200": { "$ref": "#/components/responses/ThingsPageRes" },
          "400": { "description": "Failed due to malformed query parameters." },
          "401": { "description": "Missing or invalid access token provided." },
          "404": { "description": "Model does not exist." },
          "422": { "description": "Database can't process request." },
          "500": { "$ref": "#/components/responses/ServiceError" }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Key": {
        "type": "string",
        "format": "uuid",
        "description": "Thing key that is used for thing auth. If there is\nnot one provided service will generate one in UUID\nformat.\n"
      },
      "Identity": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Thing unique identifier. This can be either provided by the user or left blank. If the user provides a UUID, it would be validated. If there is not one provided then the service will generate one in UUID format."
          }
        }
      },
      "ThingReqSchema": {
        "type": "object",
        "properties": {
          "key": { "$ref": "#/components/schemas/Key" },
          "name": {
            "type": "string",
            "description": "Free-form thing name."
          },
          "metadata": {
            "type": "object",
            "description": "Arbitrary, object-encoded thing's data."
          }
        }
      },
      "ThingsReqSchema": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "description": "Name filter. Filtering is performed as a case-insensitive partial match."
          },
          "metadata": {
            "type": "object",
            "description": "Metadata filter. Filtering is performed matching the parameter with metadata on top level. Parameter is json."
          },
          "total": {
            "type": "integer",
            "description": "Total number of items."
          },
          "offset": {
            "type": "integer",
            "description": "Number of items to skip during retrieval.",
            "default": 0,
            "minimum": 0
          },
          "limit": {
            "type": "integer",
            "description": "Size of the subset to retrieve.",
            "default": 10,
            "maximum": 100,
            "minimum": 1
          },
          "order": {
            "type": "string",
            "description": "Order type.",
            "default": "id",
            "enum": ["name", "id"]
          },
          "dir": {
            "type": "string",
            "description": "Order direction.",
            "default": "desc",
            "enum": ["asc", "desc"]
          }
        }
      },
      "ThingStatus": {
        "type": "object",
        "properties": {
          "connected": {
            "type": "boolean",
            "description": "Whether the thing is currently connected."
          },
          "connected_at": {
            "type": "integer",
            "format": "int64",
            "description": "Unix timestamp of the last connection event."
          },
          "disconnected_at": {
            "type": "integer",
            "format": "int64",
            "description": "Unix timestamp of the last disconnection event."
          },
          "last_seen": {
            "type": "integer",
            "format": "int64",
            "description": "Unix timestamp of the last time the thing was seen."
          },
          "last_message_at": {
            "type": "integer",
            "format": "int64",
            "description": "Unix timestamp of the last message received."
          },
          "connect_count": {
            "type": "integer",
            "format": "int64",
            "description": "Total number of connection events."
          },
          "disconnect_count": {
            "type": "integer",
            "format": "int64",
            "description": "Total number of disconnection events."
          },
          "message_count": {
            "type": "integer",
            "format": "int64",
            "description": "Total number of messages received."
          },
          "bytes_total": {
            "type": "integer",
            "format": "int64",
            "description": "Total bytes received."
          },
          "total_connected_time": {
            "type": "integer",
            "format": "int64",
            "description": "Total seconds the thing has been connected."
          }
        }
      },
      "StatusSummary": {
        "type": "object",
        "properties": {
          "online": {
            "type": "integer",
            "format": "int64",
            "description": "Number of currently connected devices."
          },
          "total": {
            "type": "integer",
            "format": "int64",
            "description": "Total number of things."
          },
          "never_connected": {
            "type": "integer",
            "format": "int64",
            "description": "Things that have never connected (total - known)."
          },
          "active_last_hour": {
            "type": "integer",
            "format": "int64",
            "description": "Devices seen in the last hour."
          },
          "offline": {
            "type": "integer",
            "format": "int64",
            "description": "Devices that have connected before but are currently offline."
          }
        }
      },
      "FlappingThing": {
        "type": "object",
        "properties": {
          "thing_id": {
            "type": "string",
            "description": "Thing identifier."
          },
          "reconnects": {
            "type": "integer",
            "format": "int64",
            "description": "Number of connect events within the window."
          }
        }
      },
      "FlappingResponse": {
        "type": "object",
        "properties": {
          "things": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/FlappingThing" }
          }
        }
      },
      "UptimeEvent": {
        "type": "object",
        "properties": {
          "time": {
            "type": "integer",
            "format": "int64",
            "description": "Unix timestamp of the event."
          },
          "event": {
            "type": "string",
            "enum": ["connect", "disconnect"],
            "description": "Event type."
          }
        }
      },
      "UptimeResponse": {
        "type": "object",
        "properties": {
          "uptime_seconds": {
            "type": "number",
            "format": "double",
            "description": "Total connected seconds in the requested time range."
          },
          "events": {
            "type": "array",
            "description": "Connect/disconnect events in chronological order.",
            "items": { "$ref": "#/components/schemas/UptimeEvent" }
          }
        }
      },
      "ThingResSchema": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Unique thing identifier generated by the service."
          },
          "name": {
            "type": "string",
            "description": "Free-form thing name."
          },
          "key": {
            "type": "string",
            "format": "uuid",
            "description": "Auto-generated access key."
          },
          "metadata": {
            "oneOf": [
              { "$ref": "#/components/schemas/DeviceMetadata" },
              { "$ref": "#/components/schemas/EdgeMetadata" }
            ]
          },
          "model": {
            "type": "string",
            "format": "uuid",
            "description": "Model identifier assigned to this thing."
          },
          "status": { "$ref": "#/components/schemas/ThingStatus" }
        },
        "required": ["id", "type", "key"]
      },
      "ThingsPage": {
        "type": "object",
        "properties": {
          "things": {
            "type": "array",
            "minItems": 0,
            "uniqueItems": true,
            "items": { "$ref": "#/components/schemas/ThingResSchema" }
          },
          "total": {
            "type": "integer",
            "description": "Total number of items."
          },
          "offset": {
            "type": "integer",
            "description": "Number of items to skip during retrieval."
          },
          "limit": {
            "type": "integer",
            "description": "Maximum number of items to return in one page."
          }
        },
        "required": ["Things"]
      },
      "ChannelReqSchema": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "description": "Free-form channel name."
          },
          "metadata": {
            "type": "object",
            "description": "Arbitrary, object-encoded channel's data."
          }
        }
      },
      "ChannelResSchema": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Unique channel identifier generated by the service."
          },
          "name": {
            "type": "string",
            "description": "Free-form channel name."
          },
          "metadata": {
            "type": "object",
            "description": "Arbitrary, object-encoded channel's data."
          }
        },
        "required": ["id"]
      },
      "ChannelsPage": {
        "type": "object",
        "properties": {
          "channels": {
            "type": "array",
            "minItems": 0,
            "uniqueItems": true,
            "items": { "$ref": "#/components/schemas/ChannelResSchema" }
          },
          "total": {
            "type": "integer",
            "description": "Total number of items."
          },
          "offset": {
            "type": "integer",
            "description": "Number of items to skip during retrieval."
          },
          "limit": {
            "type": "integer",
            "description": "Maximum number of items to return in one page."
          }
        },
        "required": ["Channels"]
      },
      "ConnectionReqSchema": {
        "type": "object",
        "properties": {
          "channel_ids": {
            "type": "array",
            "description": "Channel IDs.",
            "items": { "type": "string" }
          },
          "thing_ids": {
            "type": "array",
            "description": "Thing IDs",
            "items": { "type": "string" }
          }
        }
      },
      "DeviceMetadata": {
        "type": "object",
        "properties": {
          "type": { "type": "string", "enum": ["device"] },
          "control_channel_id": { "type": "string", "format": "uuid" },
          "event_channel_id": { "type": "string", "format": "uuid" }
        }
      },
      "EdgeMetadata": {
        "type": "object",
        "properties": {
          "type": { "type": "string", "enum": ["edge"] },
          "cfg_id": { "type": "string" },
          "init_id": { "type": "string" },
          "init_key": { "type": "string" },
          "control_channel_id": { "type": "string", "format": "uuid" },
          "event_channel_id": { "type": "string", "format": "uuid" },
          "description": { "type": "string" }
        }
      },
      "Model": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Unique model identifier."
          },
          "name": {
            "type": "string",
            "description": "Free-form model name."
          },
          "description": {
            "type": "string",
            "description": "Free-form model description."
          },
          "metadata": {
            "type": "object",
            "description": "Arbitrary, object-encoded model data."
          },
          "tags": {
            "type": "array",
            "items": { "type": "string" },
            "description": "List of tags associated with the model."
          },
          "tenants": {
            "type": "array",
            "items": { "type": "string" },
            "description": "List of tenants that can access this model."
          },
          "image": { "type": "string", "description": "URL to model image." },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "Time when the model was created."
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "Time when the model was last updated."
          }
        }
      }
    },
    "parameters": {
      "ChanId": {
        "name": "chanId",
        "description": "Unique channel identifier.",
        "in": "path",
        "schema": { "type": "string", "format": "uuid" },
        "required": true
      },
      "ThingId": {
        "name": "thingId",
        "description": "Unique thing identifier.",
        "in": "path",
        "schema": { "type": "string", "format": "uuid" },
        "required": true
      },
      "GroupId": {
        "name": "groupId",
        "description": "Unique group identifier.",
        "in": "path",
        "schema": { "type": "string", "format": "ulid" },
        "required": true
      },
      "Limit": {
        "name": "limit",
        "description": "Size of the subset to retrieve.",
        "in": "query",
        "schema": {
          "type": "integer",
          "default": 10,
          "maximum": 100,
          "minimum": 1
        },
        "required": false
      },
      "Offset": {
        "name": "offset",
        "description": "Number of items to skip during retrieval.",
        "in": "query",
        "schema": { "type": "integer", "default": 0, "minimum": 0 },
        "required": false
      },
      "Connected": {
        "name": "connected",
        "description": "Connection state of the subset to retrieve.",
        "in": "query",
        "schema": { "type": "boolean", "default": true },
        "required": false
      },
      "Name": {
        "name": "name",
        "description": "Name filter. Filtering is performed as a case-insensitive partial match.",
        "in": "query",
        "schema": { "type": "string" },
        "required": false
      },
      "Order": {
        "name": "order",
        "description": "Order type.",
        "in": "query",
        "schema": { "type": "string", "default": "id", "enum": ["name", "id"] },
        "required": false
      },
      "Direction": {
        "name": "dir",
        "description": "Order direction.",
        "in": "query",
        "schema": {
          "type": "string",
          "default": "desc",
          "enum": ["asc", "desc"]
        },
        "required": false
      },
      "Metadata": {
        "name": "metadata",
        "description": "Metadata filter. Filtering is performed matching the parameter with metadata on top level. Parameter is json.",
        "in": "query",
        "required": false,
        "schema": { "type": "object", "additionalProperties": {} }
      },
      "ModelId": {
        "name": "modelId",
        "description": "Unique model identifier.",
        "in": "path",
        "schema": { "type": "string", "format": "uuid" },
        "required": true
      },
      "Tag": {
        "name": "tag",
        "description": "Tag filter. Filtering is performed as a case-insensitive match.",
        "in": "query",
        "schema": { "type": "string" },
        "required": false
      },
      "Tenant": {
        "name": "tenant",
        "description": "Tenant filter. Used to filter models by specific tenant.",
        "in": "query",
        "schema": { "type": "string", "maxLength": 64 },
        "required": false
      }
    },
    "requestBodies": {
      "BatchStatusReq": {
        "description": "JSON document containing thing IDs for status lookup.",
        "required": true,
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "ids": {
                  "type": "array",
                  "items": { "type": "string", "format": "uuid" },
                  "description": "List of thing IDs to fetch statuses for."
                }
              },
              "required": ["ids"]
            }
          }
        }
      },
      "ThingCreateReq": {
        "description": "JSON-formatted document describing the new thing.",
        "required": true,
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ThingReqSchema" }
          }
        }
      },
      "ThingsCreateReq": {
        "description": "JSON-formatted document describing the new things.",
        "required": true,
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "key": { "$ref": "#/components/schemas/Key" },
                "things": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ThingReqSchema"
                  }
                }
              }
            }
          }
        }
      },
      "ThingUpdateReq": {
        "description": "Arbitrary, object-encoded thing's data.",
        "required": true,
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string",
                  "description": "Free-form thing name."
                },
                "metadata": { "type": "object" }
              }
            }
          }
        }
      },
      "ThingsSearchReq": {
        "description": "JSON-formatted document describing search parameters.",
        "required": true,
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ThingsReqSchema" }
          }
        }
      },
      "KeyUpdateReq": {
        "required": true,
        "description": "JSON containing thing.",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "key": {
                  "type": "string",
                  "format": "uuid",
                  "description": "Thing key that is used for thing auth."
                }
              }
            }
          }
        }
      },
      "ChannelCreateReq": {
        "description": "JSON-formatted document describing the updated channel.",
        "required": true,
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ChannelReqSchema" }
          }
        }
      },
      "ChannelsCreateReq": {
        "description": "JSON-formatted document describing the new channels.",
        "required": true,
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "key": { "$ref": "#/components/schemas/Key" },
                "things": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ChannelReqSchema"
                  }
                }
              }
            }
          }
        }
      },
      "ConnCreateReq": {
        "description": "JSON-formatted document describing the new connection.",
        "required": true,
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ConnectionReqSchema" }
          }
        }
      },
      "DisconnReq": {
        "description": "JSON-formatted document describing the entities for disconnection.",
        "required": true,
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ConnectionReqSchema" }
          }
        }
      },
      "AccessByIDReq": {
        "description": "JSON-formatted document that contains thing key.",
        "required": true,
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "thing_id": {
                  "type": "string",
                  "format": "uuid",
                  "description": "Thing ID by which thing is uniquely identified."
                }
              }
            }
          }
        }
      },
      "ModelCreateReq": {
        "description": "JSON-formatted document describing the new model.",
        "required": true,
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Model" }
          }
        }
      },
      "ModelUpdateReq": {
        "description": "JSON-formatted document describing model updates.",
        "required": true,
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "name": { "type": "string" },
                "description": { "type": "string" },
                "metadata": { "type": "object" },
                "image": { "type": "string" }
              }
            }
          }
        }
      },
      "ModelTagsReq": {
        "description": "JSON-formatted document containing model tags.",
        "required": true,
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "tags": {
                  "type": "array",
                  "items": { "type": "string" }
                }
              }
            }
          }
        }
      },
      "ModelTenantsReq": {
        "description": "JSON-formatted document containing model tenants.",
        "required": true,
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "tenants": {
                  "type": "array",
                  "items": { "type": "string" }
                }
              }
            }
          }
        }
      }
    },
    "responses": {
      "BatchStatusRes": {
        "description": "Map of thing IDs to their connectivity statuses.",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "statuses": {
                  "type": "object",
                  "additionalProperties": {
                    "$ref": "#/components/schemas/ThingStatus"
                  },
                  "description": "Statuses keyed by thing ID. Things that have never connected are omitted."
                }
              }
            }
          }
        }
      },
      "CreateThingRes": {
        "description": "Thing registered.",
        "headers": {
          "Location": {
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string",
                  "description": "Created thing's relative URL.",
                  "example": "/things/{thingId}"
                }
              }
            }
          }
        }
      },
      "ThingRes": {
        "description": "Data retrieved.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ThingResSchema" }
          }
        }
      },
      "ThingsPageRes": {
        "description": "Data retrieved.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ThingsPage" }
          }
        }
      },
      "ChannelCreateRes": {
        "description": "Channel created.",
        "headers": {
          "Location": {
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string",
                  "description": "Created channel's relative URL (i.e. /channels/{chanId})."
                }
              }
            }
          }
        }
      },
      "ChannelRes": {
        "description": "Data retrieved.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ChannelResSchema" }
          }
        }
      },
      "ChannelsPageRes": {
        "description": "Data retrieved.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ChannelsPage" }
          }
        }
      },
      "ConnCreateRes": {
        "description": "Thing registered.",
        "headers": {
          "Location": {
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string",
                  "description": "Created thing's relative URL.",
                  "example": "/things/{thingId}"
                }
              }
            }
          }
        }
      },
      "DisconnRes": { "description": "Things disconnected." },
      "AccessGrantedRes": {
        "description": "Thing has access to the specified channel and the thing ID is returned.\n",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Identity" }
          }
        }
      },
      "IdentityRes": {
        "description": "Thing ID returned.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Identity" }
          }
        }
      },
      "ModelCreateRes": {
        "description": "Model created successfully.",
        "headers": {
          "Location": {
            "schema": { "type": "string" },
            "description": "URL to created model resource."
          }
        }
      },
      "ModelRes": {
        "description": "Model details retrieved successfully.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Model" }
          }
        }
      },
      "ModelsPageRes": {
        "description": "List of models with pagination info.",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "total": { "type": "integer" },
                "offset": { "type": "integer" },
                "limit": { "type": "integer" },
                "models": {
                  "type": "array",
                  "items": { "$ref": "#/components/schemas/Model" }
                }
              }
            }
          }
        }
      },
      "ServiceError": {
        "description": "Unexpected server error.",
        "content": {
          "application/json": {
            "schema": { "type": "string", "format": "byte" }
          }
        }
      }
    },
    "securitySchemes": {
      "$ref": "../../../common.json#/components/securitySchemes"
    }
  },
  "security": [{ "chx_auth_session": [] }, { "bearer": [] }]
}
