{
  "openapi": "3.1.0",
  "info": {
    "title": "Клиентский API Трекер Хаб",
    "version": "1.0.0-rc2-review",
    "description": "Справочник проверенной RC2 от 18.10.2026. Публичный запуск ещё не выполнен. Адрес api.example.invalid не работает; все примеры условные, нулевые UUID и токены являются заглушками. Управление: cookie hub_customer, Origin и X-CSRF-Token из login/GET me; ресурсным POST/PATCH нужен request_id UUID. Bearer даёт только чтение выбранных собственных устройств. Выполнение запросов и ввод авторизации в этом справочнике отключены."
  },
  "servers": [
    {
      "url": "https://api.example.invalid",
      "description": "Условный нерабочий адрес. Адрес демо согласуется отдельно."
    }
  ],
  "tags": [
    {
      "name": "Аккаунт и вход"
    },
    {
      "name": "Каналы"
    },
    {
      "name": "Объекты/устройства"
    },
    {
      "name": "Токены"
    },
    {
      "name": "Чтение данных"
    }
  ],
  "paths": {
    "/api/v1/auth/register": {
      "post": {
        "operationId": "customerRegister",
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/Customer_Origin"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Customer_Credentials"
              },
              "examples": {
                "placeholder1": {
                  "summary": "Запрос с условными значениями",
                  "value": {
                    "email": "you@example.invalid",
                    "password": "YOUR_PASSWORD_AT_LEAST_15_CHARS"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/Customer_Accepted"
          },
          "default": {
            "$ref": "#/components/responses/Customer_Error"
          }
        },
        "tags": [
          "Аккаунт и вход"
        ],
        "summary": "Зарегистрировать аккаунт",
        "description": "Отправляет письмо для подтверждения email. До подтверждения нельзя создавать каналы и токены. Ответ не раскрывает, зарегистрирован ли email."
      }
    },
    "/api/v1/auth/login": {
      "post": {
        "operationId": "customerLogin",
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/Customer_Origin"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Customer_Credentials"
              },
              "examples": {
                "placeholder1": {
                  "summary": "Запрос с условными значениями",
                  "value": {
                    "email": "you@example.invalid",
                    "password": "YOUR_PASSWORD_AT_LEAST_15_CHARS"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Сессия HttpOnly/SameSite=Lax; Secure на HTTPS. 24 часа. Секрет сессии отсутствует в JSON.",
            "headers": {
              "Set-Cookie": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Customer_Account"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Customer_Error"
          }
        },
        "tags": [
          "Аккаунт и вход"
        ],
        "summary": "Войти в аккаунт",
        "description": "Возвращает cookie hub_customer и csrf_token в JSON. Cookie: HttpOnly, SameSite=Lax, Secure; сессия действует 24 часа."
      }
    },
    "/api/v1/auth/logout": {
      "post": {
        "operationId": "customerLogout",
        "parameters": [
          {
            "$ref": "#/components/parameters/Customer_Origin"
          },
          {
            "$ref": "#/components/parameters/Customer_CSRF"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/Customer_Accepted"
          },
          "default": {
            "$ref": "#/components/responses/Customer_Error"
          }
        },
        "tags": [
          "Аккаунт и вход"
        ],
        "summary": "Выйти из аккаунта",
        "description": "Нужны пользовательская сессия, точный Origin и X-CSRF-Token.",
        "security": [
          {
            "customerSession": []
          }
        ]
      }
    },
    "/api/v1/auth/email/forgot-password": {
      "post": {
        "operationId": "customerForgotPassword",
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/Customer_Origin"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Customer_Email"
              },
              "examples": {
                "placeholder1": {
                  "summary": "Запрос с условными значениями",
                  "value": {
                    "email": "you@example.invalid"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/Customer_Accepted"
          },
          "default": {
            "$ref": "#/components/responses/Customer_Error"
          }
        },
        "tags": [
          "Аккаунт и вход"
        ],
        "summary": "Запросить сброс пароля",
        "description": "Ответ не раскрывает наличие аккаунта. Одноразовое действие из письма действительно 30 минут."
      }
    },
    "/api/v1/auth/email/resend-verification": {
      "post": {
        "operationId": "customerResendVerification",
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/Customer_Origin"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Customer_Email"
              },
              "examples": {
                "placeholder1": {
                  "summary": "Запрос с условными значениями",
                  "value": {
                    "email": "you@example.invalid"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/Customer_Accepted"
          },
          "default": {
            "$ref": "#/components/responses/Customer_Error"
          }
        },
        "tags": [
          "Аккаунт и вход"
        ],
        "summary": "Повторить письмо подтверждения",
        "description": "Запрашивает письмо подтверждения email; одинаковый accepted для существующего и неизвестного email."
      }
    },
    "/api/v1/auth/action/verify-email": {
      "post": {
        "operationId": "customerVerifyEmail",
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/Customer_Origin"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Customer_Verify"
              },
              "examples": {
                "placeholder1": {
                  "summary": "Запрос с условными значениями",
                  "value": {
                    "token": "0000000000000000000000000000000000000000000000000000000000000000"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/Customer_Accepted"
          },
          "default": {
            "$ref": "#/components/responses/Customer_Error"
          }
        },
        "tags": [
          "Аккаунт и вход"
        ],
        "summary": "Подтвердить email",
        "description": "Токен берут из fragment ссылки письма и отправляют POST. GET по ссылке не меняет аккаунт. Ссылка подтверждения действует 24 часа. Нулевой токен в примере — заглушка."
      }
    },
    "/api/v1/auth/action/reset-password": {
      "post": {
        "operationId": "customerResetPassword",
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/Customer_Origin"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Customer_Reset"
              },
              "examples": {
                "placeholder1": {
                  "summary": "Запрос с условными значениями",
                  "value": {
                    "token": "0000000000000000000000000000000000000000000000000000000000000000",
                    "password": "YOUR_NEW_PASSWORD_AT_LEAST_15_CHARS"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/Customer_Accepted"
          },
          "default": {
            "$ref": "#/components/responses/Customer_Error"
          }
        },
        "tags": [
          "Аккаунт и вход"
        ],
        "summary": "Задать новый пароль по ссылке",
        "description": "Одноразовый токен из письма и новый пароль. Сброс завершает веб-сессии; API-токены отзываются отдельно."
      }
    },
    "/api/v1/me": {
      "get": {
        "operationId": "customerMe",
        "responses": {
          "200": {
            "description": "Свой аккаунт и CSRF; неподтверждённая сессия разрешена",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Customer_Account"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Customer_Error"
          }
        },
        "tags": [
          "Аккаунт и вход"
        ],
        "summary": "Получить свой аккаунт и CSRF",
        "description": "Возвращает verified, csrf_token и доступные квоты. Доступен также сессии с ещё не подтверждённым email.",
        "security": [
          {
            "customerSession": []
          }
        ]
      }
    },
    "/api/v1/me/channels": {
      "get": {
        "operationId": "customerChannels",
        "responses": {
          "200": {
            "description": "Собственные каналы",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "items"
                  ],
                  "properties": {
                    "items": {
                      "type": "array",
                      "maxItems": 128,
                      "items": {
                        "$ref": "#/components/schemas/Customer_Channel"
                      }
                    }
                  }
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Customer_Error"
          }
        },
        "tags": [
          "Каналы"
        ],
        "summary": "Получить свои каналы",
        "description": "Показывает host, port, desired_state и фактический state. Приём готов, только когда state=ready.",
        "security": [
          {
            "customerSession": []
          }
        ]
      },
      "post": {
        "operationId": "customerCreateChannel",
        "parameters": [
          {
            "$ref": "#/components/parameters/Customer_Origin"
          },
          {
            "$ref": "#/components/parameters/Customer_CSRF"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Customer_NewChannel"
              },
              "examples": {
                "placeholder1": {
                  "summary": "egts 1 tcp",
                  "value": {
                    "request_id": "00000000-0000-4000-8000-000000000001",
                    "name": "Пример EGTS",
                    "protocol": "egts",
                    "version": "1",
                    "transport": "tcp"
                  }
                },
                "placeholder2": {
                  "summary": "wialon-ips 2.0 tcp",
                  "value": {
                    "request_id": "00000000-0000-4000-8000-000000000002",
                    "name": "Пример IPS",
                    "protocol": "wialon-ips",
                    "version": "2.0",
                    "transport": "tcp"
                  }
                },
                "placeholder3": {
                  "summary": "wialon-retranslator 1.0 tcp · ACK",
                  "value": {
                    "request_id": "00000000-0000-4000-8000-000000000003",
                    "name": "Пример Retranslator",
                    "protocol": "wialon-retranslator",
                    "version": "1.0",
                    "transport": "tcp",
                    "retranslator_ack": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Порт сохранён; state provisioning до bind",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Customer_ChannelCreated"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Customer_Error"
          }
        },
        "tags": [
          "Каналы"
        ],
        "summary": "Создать канал",
        "description": "Создание возвращает provisioning. Проверьте канал через GET и дождитесь ready. EGTS1 — TCP/UDP; IPS1.1/2.0 и Retranslator1.0 — TCP. retranslator_ack относится только к Retranslator.",
        "security": [
          {
            "customerSession": []
          }
        ]
      }
    },
    "/api/v1/me/channels/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Customer_Id"
        }
      ],
      "patch": {
        "operationId": "customerUpdateChannel",
        "parameters": [
          {
            "$ref": "#/components/parameters/Customer_Origin"
          },
          {
            "$ref": "#/components/parameters/Customer_CSRF"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Customer_ChannelPatch"
              },
              "examples": {
                "placeholder1": {
                  "summary": "Запрос с условными значениями",
                  "value": {
                    "request_id": "00000000-0000-4000-8000-000000000004",
                    "name": "Новое имя канала",
                    "state": "paused"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Сохранено желаемое состояние; archived необратим",
            "порт зарезервирован": null,
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Customer_ChannelUpdated"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Customer_Error"
          }
        },
        "tags": [
          "Каналы"
        ],
        "summary": "Переименовать, приостановить или архивировать канал",
        "description": "Входное state: active, paused или archived. Ответ отражает желаемое состояние; фактическое проверьте через GET. Архивирование необратимо из кабинета, порт и история сохраняются.",
        "security": [
          {
            "customerSession": []
          }
        ]
      }
    },
    "/api/v1/me/devices": {
      "get": {
        "operationId": "customerDevices",
        "security": [
          {
            "customerSession": []
          },
          {
            "bearer": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Hub_Limit"
          },
          {
            "$ref": "#/components/parameters/Hub_Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "Разрешённые устройства",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Hub_DevicesPage"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Customer_Error"
          }
        },
        "tags": [
          "Объекты/устройства"
        ],
        "summary": "Получить доступные устройства",
        "description": "Отдельный read alias: допускает customerSession ИЛИ Bearer. С Bearer возвращает только выбранные собственные устройства. Формат страницы совпадает с GET /api/v1/devices."
      },
      "post": {
        "operationId": "customerCreateDevice",
        "parameters": [
          {
            "$ref": "#/components/parameters/Customer_Origin"
          },
          {
            "$ref": "#/components/parameters/Customer_CSRF"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Customer_NewDevice"
              },
              "examples": {
                "placeholder1": {
                  "summary": "Запрос с условными значениями",
                  "value": {
                    "request_id": "00000000-0000-4000-8000-000000000005",
                    "name": "Пример устройства",
                    "channel_id": "00000000-0000-4000-8000-000000000011",
                    "source_ident": "DEVICE_SOURCE_ID"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "ips_password только при первоначальном создании IPS устройства",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Customer_DeviceCreated"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Customer_Error"
          }
        },
        "tags": [
          "Объекты/устройства"
        ],
        "summary": "Добавить устройство в канал",
        "description": "Объект — устройство источника, а не карточка транспортной ERP. source_ident — ASCII ID источника; channel_id — UUID своего канала. Для IPS пароль показывается при первом создании.",
        "security": [
          {
            "customerSession": []
          }
        ]
      }
    },
    "/api/v1/me/devices/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Customer_Id"
        }
      ],
      "patch": {
        "operationId": "customerUpdateDevice",
        "parameters": [
          {
            "$ref": "#/components/parameters/Customer_Origin"
          },
          {
            "$ref": "#/components/parameters/Customer_CSRF"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Customer_DevicePatch"
              },
              "examples": {
                "placeholder1": {
                  "summary": "Запрос с условными значениями",
                  "value": {
                    "request_id": "00000000-0000-4000-8000-000000000006",
                    "name": "Новое имя устройства",
                    "enabled": false
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Название/допуск приёма изменены",
            "история сохранена": null,
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Customer_DeviceUpdated"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Customer_Error"
          }
        },
        "tags": [
          "Объекты/устройства"
        ],
        "summary": "Изменить имя или приём устройства",
        "description": "enabled управляет допуском приёма. История сохраняется. Чужое устройство изменить нельзя.",
        "security": [
          {
            "customerSession": []
          }
        ]
      }
    },
    "/api/v1/me/devices/{id}/password": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Customer_Id"
        }
      ],
      "post": {
        "operationId": "customerRotateIpsPassword",
        "parameters": [
          {
            "$ref": "#/components/parameters/Customer_Origin"
          },
          {
            "$ref": "#/components/parameters/Customer_CSRF"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Customer_RequestId"
              },
              "examples": {
                "placeholder1": {
                  "summary": "Запрос с условными значениями",
                  "value": {
                    "request_id": "00000000-0000-4000-8000-000000000007"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Новый ips_password только при первоначальном исполнении; прежний недействителен",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Customer_IpsRotated"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Customer_Error"
          }
        },
        "tags": [
          "Объекты/устройства"
        ],
        "summary": "Сменить пароль Wialon IPS",
        "description": "Это смена IPS-пароля, не API-токена. Новый ips_password показывается один раз; прежний пароль перестаёт действовать.",
        "security": [
          {
            "customerSession": []
          }
        ]
      }
    },
    "/api/v1/me/tokens": {
      "get": {
        "operationId": "customerTokens",
        "responses": {
          "200": {
            "description": "Метаданные своих токенов без секретов",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "items"
                  ],
                  "properties": {
                    "items": {
                      "type": "array",
                      "maxItems": 500,
                      "items": {
                        "$ref": "#/components/schemas/Customer_Token"
                      }
                    }
                  }
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Customer_Error"
          }
        },
        "tags": [
          "Токены"
        ],
        "summary": "Получить сведения о своих API-токенах",
        "description": "Возвращает metadata без секретов: устройства, срок действия и время отзыва. Старый секрет получить повторно нельзя.",
        "security": [
          {
            "customerSession": []
          }
        ]
      },
      "post": {
        "operationId": "customerCreateToken",
        "parameters": [
          {
            "$ref": "#/components/parameters/Customer_Origin"
          },
          {
            "$ref": "#/components/parameters/Customer_CSRF"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Customer_NewToken"
              },
              "examples": {
                "placeholder1": {
                  "summary": "Запрос с условными значениями",
                  "value": {
                    "request_id": "00000000-0000-4000-8000-000000000008",
                    "name": "Пример доступа скрипта",
                    "device_ids": [
                      "00000000-0000-4000-8000-000000000012"
                    ],
                    "expires_at": "2026-10-25T12:00:00Z"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Read token на выбранные собственные устройства; plaintext только при первом исполнении",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Customer_TokenCreated"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Customer_Error"
          }
        },
        "tags": [
          "Токены"
        ],
        "summary": "Выпустить новый read-токен",
        "description": "Только собственные выбранные устройства и срок до 365 суток. role не передаётся в теле: роль ответа всегда read. token показывается один раз. Для замены выпустите новый токен и отзовите старый.",
        "security": [
          {
            "customerSession": []
          }
        ]
      }
    },
    "/api/v1/me/tokens/{id}/revoke": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Customer_Id"
        }
      ],
      "post": {
        "operationId": "customerRevokeToken",
        "parameters": [
          {
            "$ref": "#/components/parameters/Customer_Origin"
          },
          {
            "$ref": "#/components/parameters/Customer_CSRF"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Customer_RequestId"
              },
              "examples": {
                "placeholder1": {
                  "summary": "Запрос с условными значениями",
                  "value": {
                    "request_id": "00000000-0000-4000-8000-000000000009"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Отзыв действует со следующего запроса",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Customer_TokenRevoked"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Customer_Error"
          }
        },
        "tags": [
          "Токены"
        ],
        "summary": "Отозвать API-токен",
        "description": "Отзыв действует на следующий запрос. Используйте token_id; отдельного endpoint regenerate/rotate для API-токена нет.",
        "security": [
          {
            "customerSession": []
          }
        ]
      }
    },
    "/api/v1/devices": {
      "get": {
        "operationId": "listDevices",
        "parameters": [
          {
            "$ref": "#/components/parameters/Hub_Limit"
          },
          {
            "$ref": "#/components/parameters/Hub_Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "Разрешённые устройства",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Hub_DevicesPage"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Hub_Error"
          }
        },
        "tags": [
          "Чтение данных"
        ],
        "summary": "Получить список устройств",
        "description": "Только устройства, разрешённые Bearer-токену. Страница items/next_cursor, limit по умолчанию100, максимум500.",
        "security": [
          {
            "bearer": []
          }
        ]
      }
    },
    "/api/v1/devices/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Hub_Id"
        }
      ],
      "get": {
        "operationId": "getDevice",
        "responses": {
          "200": {
            "description": "Устройство без credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Hub_DeviceView"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Hub_Error"
          }
        },
        "tags": [
          "Чтение данных"
        ],
        "summary": "Получить карточку устройства",
        "description": "id — внутренний UUID, не IMEI. Ответ содержит metadata и aliases без credentials.",
        "security": [
          {
            "bearer": []
          }
        ]
      }
    },
    "/api/v1/devices/{id}/telemetry": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Hub_Id"
        }
      ],
      "get": {
        "operationId": "getTelemetry",
        "responses": {
          "200": {
            "description": "Последние значения и свежесть",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Hub_Telemetry"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Hub_Error"
          }
        },
        "tags": [
          "Чтение данных"
        ],
        "summary": "Получить последние значения",
        "description": "last_received_at отражает последнее поступление; values содержит отдельные timestamp, timestamp_source, message_id и device_seq для каждого значения.",
        "security": [
          {
            "bearer": []
          }
        ]
      }
    },
    "/api/v1/devices/{id}/messages": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Hub_Id"
        }
      ],
      "get": {
        "operationId": "listMessages",
        "parameters": [
          {
            "$ref": "#/components/parameters/Hub_Limit"
          },
          {
            "$ref": "#/components/parameters/Hub_Cursor"
          },
          {
            "name": "from",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time",
              "pattern": "Z$"
            }
          },
          {
            "name": "to",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time",
              "pattern": "Z$"
            }
          },
          {
            "name": "after_seq",
            "in": "query",
            "schema": {
              "type": "string",
              "pattern": "^(0|[1-9][0-9]{0,19})$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Фиксированная история или incremental",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Hub_MessagesPage"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Hub_Error"
          }
        },
        "tags": [
          "Чтение данных"
        ],
        "summary": "Получить историю или новые сообщения",
        "description": "from/to фильтруют received_at в UTC. История — по убыванию device_seq, after_seq — отдельный incremental режим по возрастанию. Cursor привязан к правам/фильтрам; после смены прав начните пагинацию заново.",
        "security": [
          {
            "bearer": []
          }
        ]
      }
    }
  },
  "components": {
    "parameters": {
      "Customer_Origin": {
        "in": "header",
        "name": "Origin",
        "required": true,
        "schema": {
          "type": "string"
        },
        "description": "Точное совпадение с configured customer origin"
      },
      "Customer_CSRF": {
        "in": "header",
        "name": "X-CSRF-Token",
        "required": true,
        "schema": {
          "type": "string"
        },
        "description": "Из login или GET me; Bearer не заменяет сессию"
      },
      "Customer_Id": {
        "in": "path",
        "name": "id",
        "required": true,
        "schema": {
          "$ref": "#/components/schemas/Customer_Id"
        }
      },
      "Hub_Limit": {
        "name": "limit",
        "in": "query",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 500,
          "default": 100
        }
      },
      "Hub_Cursor": {
        "name": "cursor",
        "in": "query",
        "schema": {
          "type": "string",
          "maxLength": 4096
        }
      },
      "Hub_Id": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      }
    },
    "schemas": {
      "Customer_Credentials": {
        "type": "object",
        "required": [
          "email",
          "password"
        ],
        "additionalProperties": false,
        "properties": {
          "email": {
            "type": "string",
            "format": "email",
            "maxLength": 254
          },
          "password": {
            "$ref": "#/components/schemas/Customer_Password"
          }
        }
      },
      "Customer_Password": {
        "type": "string",
        "minLength": 15,
        "maxLength": 128,
        "writeOnly": true
      },
      "Customer_Account": {
        "type": "object",
        "required": [
          "account_id",
          "email",
          "verified",
          "csrf_token"
        ],
        "additionalProperties": false,
        "properties": {
          "account_id": {
            "$ref": "#/components/schemas/Customer_Id"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "verified": {
            "type": "boolean"
          },
          "csrf_token": {
            "$ref": "#/components/schemas/Customer_Secret"
          },
          "quotas": {
            "type": "object",
            "required": [
              "channels",
              "devices",
              "tokens"
            ],
            "additionalProperties": false,
            "properties": {
              "channels": {
                "type": "integer"
              },
              "devices": {
                "type": "integer"
              },
              "tokens": {
                "type": "integer"
              }
            }
          }
        }
      },
      "Customer_Id": {
        "type": "string",
        "format": "uuid"
      },
      "Customer_Secret": {
        "type": "string",
        "pattern": "^[0-9a-f]{64}$"
      },
      "Customer_Email": {
        "type": "object",
        "required": [
          "email"
        ],
        "additionalProperties": false,
        "properties": {
          "email": {
            "type": "string",
            "format": "email",
            "maxLength": 254
          }
        }
      },
      "Customer_Verify": {
        "type": "object",
        "required": [
          "token"
        ],
        "additionalProperties": false,
        "properties": {
          "token": {
            "$ref": "#/components/schemas/Customer_Secret"
          }
        }
      },
      "Customer_Reset": {
        "type": "object",
        "required": [
          "token",
          "password"
        ],
        "additionalProperties": false,
        "properties": {
          "token": {
            "$ref": "#/components/schemas/Customer_Secret"
          },
          "password": {
            "$ref": "#/components/schemas/Customer_Password"
          }
        }
      },
      "Customer_Channel": {
        "type": "object",
        "required": [
          "id",
          "name",
          "protocol",
          "version",
          "transport",
          "host",
          "port",
          "desired_state",
          "state",
          "last_error",
          "retranslator_ack"
        ],
        "additionalProperties": false,
        "properties": {
          "id": {
            "$ref": "#/components/schemas/Customer_Id"
          },
          "name": {
            "$ref": "#/components/schemas/Customer_Name"
          },
          "protocol": {
            "enum": [
              "egts",
              "wialon-ips",
              "wialon-retranslator"
            ]
          },
          "version": {
            "type": "string"
          },
          "transport": {
            "enum": [
              "tcp",
              "udp"
            ]
          },
          "host": {
            "type": "string"
          },
          "port": {
            "type": "integer",
            "minimum": 1024,
            "maximum": 65535
          },
          "desired_state": {
            "enum": [
              "active",
              "paused",
              "archived"
            ]
          },
          "state": {
            "enum": [
              "provisioning",
              "ready",
              "paused",
              "error",
              "archived"
            ]
          },
          "last_error": {
            "type": [
              "string",
              "null"
            ]
          },
          "retranslator_ack": {
            "type": "boolean"
          },
          "last_received_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "last_ingress_error": {
            "type": [
              "string",
              "null"
            ]
          },
          "received": {
            "type": "integer",
            "minimum": 0
          },
          "stored": {
            "type": "integer",
            "minimum": 0
          }
        }
      },
      "Customer_Name": {
        "type": "string",
        "minLength": 1,
        "maxLength": 128
      },
      "Customer_NewChannel": {
        "type": "object",
        "required": [
          "request_id",
          "name",
          "protocol",
          "version",
          "transport"
        ],
        "additionalProperties": false,
        "properties": {
          "request_id": {
            "$ref": "#/components/schemas/Customer_Id"
          },
          "name": {
            "$ref": "#/components/schemas/Customer_Name"
          },
          "protocol": {
            "enum": [
              "egts",
              "wialon-ips",
              "wialon-retranslator"
            ]
          },
          "version": {
            "enum": [
              "1",
              "1.1",
              "2.0",
              "1.0"
            ]
          },
          "transport": {
            "enum": [
              "tcp",
              "udp"
            ]
          },
          "retranslator_ack": {
            "type": "boolean",
            "default": false
          }
        },
        "description": "Допустимы EGTS1 TCP/UDP, IPS1.1/2.0 TCP, Retranslator1.0 TCP. ACK настройка только Retranslator."
      },
      "Customer_ChannelCreated": {
        "type": "object",
        "required": [
          "id",
          "port",
          "host",
          "state"
        ],
        "additionalProperties": false,
        "properties": {
          "id": {
            "$ref": "#/components/schemas/Customer_Id"
          },
          "port": {
            "type": "integer"
          },
          "host": {
            "type": "string"
          },
          "state": {
            "const": "provisioning"
          }
        }
      },
      "Customer_ChannelPatch": {
        "type": "object",
        "required": [
          "request_id"
        ],
        "additionalProperties": false,
        "properties": {
          "request_id": {
            "$ref": "#/components/schemas/Customer_Id"
          },
          "name": {
            "$ref": "#/components/schemas/Customer_Name"
          },
          "state": {
            "enum": [
              "active",
              "paused",
              "archived"
            ]
          }
        }
      },
      "Customer_ChannelUpdated": {
        "type": "object",
        "required": [
          "id",
          "desired_state"
        ],
        "additionalProperties": false,
        "properties": {
          "id": {
            "$ref": "#/components/schemas/Customer_Id"
          },
          "desired_state": {
            "enum": [
              "active",
              "paused",
              "archived"
            ]
          }
        }
      },
      "Hub_DevicesPage": {
        "type": "object",
        "required": [
          "items",
          "next_cursor"
        ],
        "properties": {
          "items": {
            "type": "array",
            "maxItems": 500,
            "items": {
              "$ref": "#/components/schemas/Hub_DeviceView"
            }
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "Hub_DeviceView": {
        "type": "object",
        "required": [
          "id",
          "name",
          "enabled",
          "metadata",
          "aliases",
          "last_received_at"
        ],
        "additionalProperties": false,
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "enabled": {
            "type": "boolean"
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "aliases": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "channel_id",
                "source_ident"
              ],
              "additionalProperties": false,
              "properties": {
                "channel_id": {
                  "type": "string",
                  "format": "uuid"
                },
                "source_ident": {
                  "type": "string"
                }
              }
            }
          },
          "last_received_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "Customer_NewDevice": {
        "type": "object",
        "required": [
          "request_id",
          "name",
          "channel_id",
          "source_ident"
        ],
        "additionalProperties": false,
        "properties": {
          "request_id": {
            "$ref": "#/components/schemas/Customer_Id"
          },
          "name": {
            "$ref": "#/components/schemas/Customer_Name"
          },
          "channel_id": {
            "$ref": "#/components/schemas/Customer_Id"
          },
          "source_ident": {
            "type": "string",
            "pattern": "^[A-Za-z0-9._-]{1,128}$"
          }
        }
      },
      "Customer_DeviceCreated": {
        "type": "object",
        "required": [
          "id",
          "channel_id",
          "source_ident"
        ],
        "additionalProperties": false,
        "properties": {
          "id": {
            "$ref": "#/components/schemas/Customer_Id"
          },
          "channel_id": {
            "$ref": "#/components/schemas/Customer_Id"
          },
          "source_ident": {
            "type": "string"
          },
          "ips_password": {
            "$ref": "#/components/schemas/Customer_Secret"
          }
        }
      },
      "Customer_DevicePatch": {
        "type": "object",
        "required": [
          "request_id"
        ],
        "additionalProperties": false,
        "properties": {
          "request_id": {
            "$ref": "#/components/schemas/Customer_Id"
          },
          "name": {
            "$ref": "#/components/schemas/Customer_Name"
          },
          "enabled": {
            "type": "boolean"
          }
        }
      },
      "Customer_DeviceUpdated": {
        "type": "object",
        "required": [
          "id",
          "updated"
        ],
        "additionalProperties": false,
        "properties": {
          "id": {
            "$ref": "#/components/schemas/Customer_Id"
          },
          "updated": {
            "const": true
          }
        }
      },
      "Customer_RequestId": {
        "type": "object",
        "required": [
          "request_id"
        ],
        "additionalProperties": false,
        "properties": {
          "request_id": {
            "$ref": "#/components/schemas/Customer_Id"
          }
        }
      },
      "Customer_IpsRotated": {
        "type": "object",
        "required": [
          "id",
          "rotated"
        ],
        "additionalProperties": false,
        "properties": {
          "id": {
            "$ref": "#/components/schemas/Customer_Id"
          },
          "rotated": {
            "const": true
          },
          "ips_password": {
            "$ref": "#/components/schemas/Customer_Secret"
          }
        }
      },
      "Customer_Token": {
        "type": "object",
        "required": [
          "id",
          "name",
          "device_ids",
          "expires_at",
          "revoked_at"
        ],
        "additionalProperties": false,
        "properties": {
          "id": {
            "$ref": "#/components/schemas/Customer_Id"
          },
          "name": {
            "$ref": "#/components/schemas/Customer_Name"
          },
          "device_ids": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Customer_Id"
            }
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "revoked_at": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "Customer_NewToken": {
        "type": "object",
        "required": [
          "request_id",
          "name",
          "device_ids",
          "expires_at"
        ],
        "additionalProperties": false,
        "properties": {
          "request_id": {
            "$ref": "#/components/schemas/Customer_Id"
          },
          "name": {
            "$ref": "#/components/schemas/Customer_Name"
          },
          "device_ids": {
            "type": "array",
            "minItems": 1,
            "uniqueItems": true,
            "items": {
              "$ref": "#/components/schemas/Customer_Id"
            }
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "description": "Срок больше текущего времени, максимум 365 суток."
      },
      "Customer_TokenCreated": {
        "type": "object",
        "required": [
          "token_id",
          "role",
          "device_ids",
          "expires_at"
        ],
        "additionalProperties": false,
        "properties": {
          "token_id": {
            "$ref": "#/components/schemas/Customer_Id"
          },
          "role": {
            "const": "read"
          },
          "device_ids": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Customer_Id"
            }
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "token": {
            "$ref": "#/components/schemas/Customer_Secret"
          }
        }
      },
      "Customer_TokenRevoked": {
        "type": "object",
        "required": [
          "token_id",
          "revoked"
        ],
        "additionalProperties": false,
        "properties": {
          "token_id": {
            "$ref": "#/components/schemas/Customer_Id"
          },
          "revoked": {
            "const": true
          }
        }
      },
      "Hub_Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "additionalProperties": false,
        "properties": {
          "error": {
            "type": "string"
          },
          "request_id": {
            "type": "string",
            "format": "uuid"
          }
        }
      },
      "Hub_Telemetry": {
        "type": "object",
        "required": [
          "device_id",
          "last_received_at",
          "values"
        ],
        "properties": {
          "device_id": {
            "type": "string",
            "format": "uuid"
          },
          "last_received_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "values": {
            "type": "object",
            "additionalProperties": {
              "type": "object",
              "required": [
                "value",
                "timestamp",
                "timestamp_source",
                "message_id",
                "device_seq"
              ],
              "properties": {
                "value": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/HubMessage_value"
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "timestamp": {
                  "type": "string",
                  "format": "date-time",
                  "pattern": "Z$"
                },
                "timestamp_source": {
                  "enum": [
                    "device",
                    "server"
                  ]
                },
                "message_id": {
                  "type": "string",
                  "format": "uuid"
                },
                "device_seq": {
                  "type": "string",
                  "pattern": "^[1-9][0-9]{0,19}$"
                }
              }
            }
          }
        }
      },
      "HubMessage_value": {
        "oneOf": [
          {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "type",
              "value"
            ],
            "properties": {
              "type": {
                "const": "integer"
              },
              "value": {
                "type": "string",
                "pattern": "^(0|-?[1-9][0-9]{0,18})$",
                "format": "int64-string"
              }
            }
          },
          {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "type",
              "value"
            ],
            "properties": {
              "type": {
                "const": "number"
              },
              "value": {
                "type": "number"
              }
            }
          },
          {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "type",
              "value"
            ],
            "properties": {
              "type": {
                "const": "boolean"
              },
              "value": {
                "type": "boolean"
              }
            }
          },
          {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "type",
              "value"
            ],
            "properties": {
              "type": {
                "const": "string"
              },
              "value": {
                "type": "string",
                "maxLength": 4096,
                "pattern": "^[^\\x00]*$"
              }
            }
          }
        ]
      },
      "Hub_MessagesPage": {
        "type": "object",
        "required": [
          "items",
          "next_cursor",
          "next_after_seq"
        ],
        "properties": {
          "items": {
            "type": "array",
            "maxItems": 500,
            "items": {
              "$ref": "#/components/schemas/HubMessage_Message"
            }
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ]
          },
          "next_after_seq": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "HubMessage_Message": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "title": "Трекер Хаб: сообщение 1.x",
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schema_family",
          "schema_version",
          "message_id",
          "device_id",
          "device_seq",
          "channel_id",
          "protocol",
          "protocol_version",
          "source_ident",
          "timestamp",
          "timestamp_source",
          "time_quality",
          "received_at",
          "position",
          "parameters",
          "source_metadata"
        ],
        "properties": {
          "schema_family": {
            "const": "tracker-hub-message"
          },
          "schema_version": {
            "type": "string",
            "pattern": "^1\\.[0-9]+\\.[0-9]+$"
          },
          "message_id": {
            "type": "string",
            "format": "uuid"
          },
          "device_id": {
            "type": "string",
            "format": "uuid"
          },
          "channel_id": {
            "type": "string",
            "format": "uuid"
          },
          "device_seq": {
            "type": "string",
            "pattern": "^[1-9][0-9]{0,19}$",
            "format": "uint64-string"
          },
          "protocol": {
            "enum": [
              "egts",
              "wialon-ips",
              "wialon-retranslator"
            ]
          },
          "protocol_version": {
            "enum": [
              "1",
              "1.0",
              "1.1",
              "2.0"
            ]
          },
          "source_ident": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128,
            "pattern": "^[\\x21-\\x7e]+$"
          },
          "timestamp": {
            "$ref": "#/components/schemas/HubMessage_utc"
          },
          "timestamp_source": {
            "enum": [
              "device",
              "server"
            ]
          },
          "time_quality": {
            "enum": [
              "ok",
              "suspect"
            ]
          },
          "received_at": {
            "$ref": "#/components/schemas/HubMessage_utc"
          },
          "position": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/HubMessage_position"
              }
            ]
          },
          "parameters": {
            "$ref": "#/components/schemas/HubMessage_parameters"
          },
          "source_metadata": {
            "$ref": "#/components/schemas/HubMessage_parameters"
          }
        },
        "allOf": [
          {
            "if": {
              "properties": {
                "protocol": {
                  "const": "egts"
                }
              }
            },
            "then": {
              "properties": {
                "protocol_version": {
                  "const": "1"
                }
              }
            }
          },
          {
            "if": {
              "properties": {
                "protocol": {
                  "const": "wialon-ips"
                }
              }
            },
            "then": {
              "properties": {
                "protocol_version": {
                  "enum": [
                    "1.1",
                    "2.0"
                  ]
                }
              }
            }
          },
          {
            "if": {
              "properties": {
                "protocol": {
                  "const": "wialon-retranslator"
                }
              }
            },
            "then": {
              "properties": {
                "protocol_version": {
                  "const": "1.0"
                }
              }
            }
          }
        ],
        "$defs": {
          "utc": {
            "type": "string",
            "format": "date-time",
            "pattern": "Z$"
          },
          "position": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "latitude",
              "longitude",
              "crs",
              "valid",
              "speed_kmh",
              "course_deg",
              "altitude_m",
              "satellites",
              "hdop"
            ],
            "properties": {
              "latitude": {
                "type": "number",
                "minimum": -90,
                "maximum": 90
              },
              "longitude": {
                "type": "number",
                "minimum": -180,
                "maximum": 180
              },
              "crs": {
                "enum": [
                  "WGS84",
                  "PZ-90.02"
                ]
              },
              "valid": {
                "type": [
                  "boolean",
                  "null"
                ]
              },
              "speed_kmh": {
                "type": [
                  "number",
                  "null"
                ],
                "minimum": 0
              },
              "course_deg": {
                "type": [
                  "number",
                  "null"
                ],
                "minimum": 0,
                "maximum": 359
              },
              "altitude_m": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "satellites": {
                "type": [
                  "integer",
                  "null"
                ],
                "minimum": 0,
                "maximum": 255
              },
              "hdop": {
                "type": [
                  "number",
                  "null"
                ],
                "minimum": 0
              }
            }
          },
          "parameters": {
            "type": "object",
            "maxProperties": 1024,
            "propertyNames": {
              "minLength": 1,
              "maxLength": 128,
              "pattern": "^[^\\x00-\\x1f\\x7f]+$"
            },
            "additionalProperties": {
              "$ref": "#/components/schemas/HubMessage_value"
            }
          },
          "value": {
            "oneOf": [
              {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "type",
                  "value"
                ],
                "properties": {
                  "type": {
                    "const": "integer"
                  },
                  "value": {
                    "type": "string",
                    "pattern": "^(0|-?[1-9][0-9]{0,18})$",
                    "format": "int64-string"
                  }
                }
              },
              {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "type",
                  "value"
                ],
                "properties": {
                  "type": {
                    "const": "number"
                  },
                  "value": {
                    "type": "number"
                  }
                }
              },
              {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "type",
                  "value"
                ],
                "properties": {
                  "type": {
                    "const": "boolean"
                  },
                  "value": {
                    "type": "boolean"
                  }
                }
              },
              {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "type",
                  "value"
                ],
                "properties": {
                  "type": {
                    "const": "string"
                  },
                  "value": {
                    "type": "string",
                    "maxLength": 4096,
                    "pattern": "^[^\\x00]*$"
                  }
                }
              }
            ]
          }
        }
      },
      "HubMessage_utc": {
        "type": "string",
        "format": "date-time",
        "pattern": "Z$"
      },
      "HubMessage_position": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "latitude",
          "longitude",
          "crs",
          "valid",
          "speed_kmh",
          "course_deg",
          "altitude_m",
          "satellites",
          "hdop"
        ],
        "properties": {
          "latitude": {
            "type": "number",
            "minimum": -90,
            "maximum": 90
          },
          "longitude": {
            "type": "number",
            "minimum": -180,
            "maximum": 180
          },
          "crs": {
            "enum": [
              "WGS84",
              "PZ-90.02"
            ]
          },
          "valid": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "speed_kmh": {
            "type": [
              "number",
              "null"
            ],
            "minimum": 0
          },
          "course_deg": {
            "type": [
              "number",
              "null"
            ],
            "minimum": 0,
            "maximum": 359
          },
          "altitude_m": {
            "type": [
              "number",
              "null"
            ]
          },
          "satellites": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 255
          },
          "hdop": {
            "type": [
              "number",
              "null"
            ],
            "minimum": 0
          }
        }
      },
      "HubMessage_parameters": {
        "type": "object",
        "maxProperties": 1024,
        "propertyNames": {
          "minLength": 1,
          "maxLength": 128,
          "pattern": "^[^\\x00-\\x1f\\x7f]+$"
        },
        "additionalProperties": {
          "$ref": "#/components/schemas/HubMessage_value"
        }
      }
    },
    "responses": {
      "Customer_Accepted": {
        "description": "Запрос принят; регистрация/запрос письма не раскрывает существование email",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "required": [
                "accepted"
              ],
              "additionalProperties": false,
              "properties": {
                "accepted": {
                  "const": true
                }
              }
            }
          }
        }
      },
      "Customer_Error": {
        "description": "400 ввод/link, 401 auth, 403 scope/Origin/CSRF, 409 conflict, 410 cursor, 413 body, 429 quota/rate, 503 backend/capacity",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "required": [
                "error"
              ],
              "additionalProperties": false,
              "properties": {
                "error": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "Hub_Error": {
        "description": "Явная ошибка запроса/доступа/состояния/backend",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Hub_Error"
            }
          }
        }
      }
    },
    "securitySchemes": {
      "customerSession": {
        "type": "apiKey",
        "in": "cookie",
        "name": "hub_customer",
        "description": "Cookie hub_customer из login. Изменения требуют Origin и X-CSRF-Token; Bearer не заменяет сессию."
      },
      "bearer": {
        "type": "http",
        "scheme": "bearer",
        "description": "Только чтение выбранных собственных устройств. Срок и отзыв проверяются при каждом запросе."
      }
    }
  },
  "x-contracts-source": {
    "commit": "1bc6b2b37547c21d01dadc590601ee54fac889bb",
    "files": {
      "contracts/openapi/customer-v1.yaml": "e6416ce88d9fb94d2d29f3e0975a264b77195181eb958865741192184d17fc2f",
      "contracts/openapi/hub-v1.yaml": "798c8c34cb12479a3abd32a7a26d12eafa0fd293fb4a02884c913d7f9bece61c",
      "contracts/schemas/hub-message-1.x.schema.json": "00ae6f9490536651ffe47d1b5f5a13b0b6809e8fdee69492215d71d1bf2cafb0"
    }
  }
}
