Skip to content

HTTP API портала: привязка устройства (Link ↔ iDryer Portal)

Разработчик продукта: вы реализуете клиента этого API в своей прошивке моста (или используете слой из библиотеки), а не свой портал — вводный текст 00-for-product-developers.md.

Документ описывает контракт публичного backend iDryer Portal для прошивки LINK (и совместимых мостов): provision, PIN, claim, проверка статуса. Сверено с кодом backend (devices.controller.ts, devices.service.ts, mqtt-auth.service.ts) и прокси nginx (location /api/ → корень Nest).

Расширенные сценарии (матрица unlink/remove, угрозы provision): в репозитории портала docs/development/DEVICE_CLAIMING_PROTOCOL.md, docs/development/LINK_CLAIM_SCENARIOS.md.


Базовый URL

Окружение API (HTTPS)
Продакшен https://portal.idryer.org/api
Локальная разработка (Nest без nginx) http://localhost:3000 (пути без префикса /api)

Пути ниже указаны относительно базы: например, POST /devices/provision → прод: https://portal.idryer.org/api/devices/provision.

Заголовок: Content-Type: application/json для POST.


Идентификатор serialNumber

Используется в provision, как username для MQTT и в префиксе топиков idryer/{serialNumber}/….

Формат (валидация ProvisionDeviceDto):

  • DEVICE_{mac} или DEVICE_{mac}_{suffix} (типичный ESP32 Link),
  • либо ровно 16 hex-символов (0-9A-Fa-f) — RP2040 mcuSerial.

Строка должна совпадать с той, что мост объявляет облаку (и кладёт в MQTT client id / username).


1. POST /devices/provision

Auth: не требуется. Rate limit: 10 запросов / 60 с (throttle на контроллере).

Тело:

{ "serialNumber": "DEVICE_aabbccddeeff_1234567" }

Ответ 200/201 (новый или существующий Link UNCLAIMED):

{
  "deviceToken": "<секрет, хранить в NVS>",
  "serialNumber": "<тот же serial>",
  "isNew": true,
  "isClaimed": false
}
- Повторный provision для того же serial в состоянии UNCLAIMED возвращает тот же deviceToken, isNew: false.

Уже привязанное устройство (Link CLAIMED / BOUND или legacy Device с тем же serial): токен не выдаётся (безопасность):

{
  "deviceToken": null,
  "serialNumber": "<serial>",
  "isNew": false,
  "isClaimed": true
}
Прошивка должна вести пользователя по сценарию recovery (удаление устройства в приложении и повторная привязка) — см. портал LINK_CLAIM_SCENARIOS.md.


2. POST /devices/register

Auth: не требуется. Rate limit: 10 / 60 с.

Тело:

{
  "token": "<deviceToken из provision>",
  "serialNumber": "<опционально; обязателен, если записи Link по token ещё нет>"
}

Ответ A — нужен PIN на экране (200):

{
  "pin": "12345678",
  "expiresAt": "2026-03-31T12:10:00.000Z",
  "remainingSeconds": 600
}
- PIN: 8 цифр, TTL 10 минут. Повторный register в пределах TTL возвращает тот же PIN (обновляется lastSeen).

Ответ B — устройство уже привязано (recovery, 200):

{
  "alreadyClaimed": true,
  "deviceId": "<uuid сушилки>",
  "serialNumber": "DEVICE_..."
}
Новый PIN не выдаётся; дальше — MQTT и/или опрос check-claim по согласованной с прошивкой логике.

Ошибки: 400 — нет Link по token и не передан serialNumber (нужен сначала provision или serial в теле).


3. POST /devices/claim

Auth: обязателен — Authorization: Bearer <user JWT> (веб/приложение пользователя, не токен устройства).

Тело:

{
  "pin": "12345678",
  "name": "Моя сушилка"
}

Успех (201):

{
  "deviceId": "<uuid>",
  "serialNumber": "<serial>",
  "name": "Моя сушилка",
  "claimed": true
}

Ошибки (типичные): - 404 — неверный PIN, - 400 — PIN истёк или устройство уже привязано, - 401 — нет/невалидный пользовательский JWT.

После успешного claim запись Device создаётся в состоянии ожидания первого MQTT info (CLAIMED_PENDING_BIND), затем переходит в активное после bind — см. портал LINK_CLAIM_SCENARIOS.md.


4. GET /devices/check-claim/:token

Auth: не требуется. :tokenLink.token (тот же deviceToken из NVS).

  • Пока пользователь не завершил claim: 404 и тело { "claimed": false }.
  • После claim (Link CLAIMED/BOUND с activeDryerId): 200 и { "claimed": true, "deviceId": "<uuid>" }.

5. POST /devices/cleanup-expired

Служебная очистка просроченных UNCLAIMED Link (и legacy-таблицы). Auth: JWT пользователя с ролью ADMIN или SUPERUSER (не публичный endpoint).

Ответ: { "deleted": <number> }.


На стороне MCU/LINK последовательность согласуется с Основные потоки: ClaimStart → HTTP provision/register → ClaimStatus (PIN) → пользователь вводит PIN в портале → опрос check-claimClaimComplete.


См. также

  • MQTT API — аутентификация на брокере (username / password)
  • Основные потоки — claiming в контексте UART
  • Репозиторий портала: docs/development/DEVICE_CLAIMING_PROTOCOL.md, docs/development/LINK_CLAIM_SCENARIOS.md, docs/development/PROVISION_SECURITY_DESIGN.md