# Контролёры на входе

Подключите телефон контролёра по QR-приглашению, переименуйте или отзовите его и получите отчёт о проходах.

Сканер на входе работает в браузере телефона, приложение ставить не нужно. Телефон заранее скачивает список билетов и проверяет подпись QR сам, поэтому пускает гостей и без интернета. Проходы отправляются на сервер, когда связь вернётся.

## Как подключить телефон {#pairing}

1. Создайте приглашение: [`POST /v1/devices/invites`](#invite). Покажите контролёру QR из ответа на экране.
2. Контролёр сканирует QR обычной камерой. Откроется сканер, и телефон подключится. Входить и придумывать пароль не нужно.
3. Приглашение живёт 15 минут и срабатывает один раз. Подключённый телефон работает, пока его не [отзовут](#revoke).

Устройство работает в режиме ключа, которым создано приглашение: тестовое видит только тестовые события и билеты.

## Пригласить устройство {#invite}

`POST /v1/devices/invites`

| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
| `name` | string | нет | Имя входа, до 60 символов. По умолчанию «Вход N» |
| `event_ids` | array | нет | События, которые устройство может проверять. Пусто: все события организации в режиме ключа |

```bash Запрос
curl https://api.budu.events/v1/devices/invites \
  -H "Authorization: Bearer $BUDU_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Вход 1", "event_ids": ["0193e0b2-4a7c-7d10-9e3f-2c5a8b1d4e60"]}'
```

```json Ответ 201
{
  "id": "0193e0e2-5b10-7c20-9d30-4e5f6a7b8c9d",
  "name": "Вход 1",
  "url": "https://budu.events/scan/#i=bi_…",
  "qr_svg": "<svg …>…</svg>",
  "expires_at": "2026-12-12T15:15:00Z",
  "event_ids": ["0193e0b2-4a7c-7d10-9e3f-2c5a8b1d4e60"]
}
```

`qr_svg` — готовый QR для экрана, `url` — та же ссылка текстом. Они показываются один раз, в списке устройств их нет. `Idempotency-Key` здесь не действует: каждый вызов создаёт новое приглашение. Событие другой организации или режима даёт `404`.

## Список устройств {#list}

`GET /v1/devices`

```bash Запрос
curl https://api.budu.events/v1/devices -H "Authorization: Bearer $BUDU_KEY"
```

```json Ответ 200
{
  "devices": [
    {
      "device": {
        "id": "0193e0e2-5b10-7c20-9d30-4e5f6a7b8c9d",
        "name": "Вход 1",
        "org_id": "0193d9a1-2b3c-7d4e-8f50-61728394a5b6",
        "livemode": false,
        "event_ids": ["0193e0b2-4a7c-7d10-9e3f-2c5a8b1d4e60"]
      },
      "status": "active",
      "created_at_ms": "1797086400000",
      "paired_at_ms": "1797086520000",
      "last_seen_at_ms": "1797098100000",
      "revoked_at_ms": "0"
    }
  ]
}
```

`status`: `pending` (ждёт подключения), `active` (подключено), `expired` (приглашение истекло неиспользованным), `revoked` (отозвано).

## Переименовать {#rename}

`PATCH /v1/devices/{id}`

```bash Запрос
curl -X PATCH https://api.budu.events/v1/devices/0193e0e2-5b10-7c20-9d30-4e5f6a7b8c9d \
  -H "Authorization: Bearer $BUDU_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Главный вход"}'
```

`name` — от 1 до 60 символов, без переносов строк. Ответ `200` с устройством, как в списке.

## Отозвать {#revoke}

`DELETE /v1/devices/{id}`

```bash Запрос
curl -X DELETE https://api.budu.events/v1/devices/0193e0e2-5b10-7c20-9d30-4e5f6a7b8c9d \
  -H "Authorization: Bearer $BUDU_KEY"
```

Ответ `204`, повтор тоже `204`. Телефон перестаёт работать в течение 10 секунд: при следующем обращении к серверу сканер стирает скачанный список билетов. Проходы, которые он сделал без интернета до отзыва, ещё 24 часа принимаются на сервер и попадают в [отчёт](#checkin).

## Отчёт о проходах {#checkin}

`GET /v1/events/{id}/checkin`

```bash Запрос
curl https://api.budu.events/v1/events/0193e0b2-4a7c-7d10-9e3f-2c5a8b1d4e60/checkin \
  -H "Authorization: Bearer $BUDU_KEY"
```

```json Ответ 200
{
  "counter": {"total": 212, "passed": 187},
  "invalid_scans": 3,
  "double_passes": [
    {
      "ticket_id": "0193e0c5-1f6b-7e02-a3c9-5b8d1e4f9f0a",
      "number": "1E4F9F0A",
      "holder": "Мария Соколова",
      "passes": [
        {
          "scan_id": "s-81f2",
          "entrance": "Вход 1",
          "scanned_at_ms": "1797096132000",
          "offline": true,
          "revoked_device": false,
          "manual": false
        },
        {
          "scan_id": "s-0c4a",
          "entrance": "Вход 2",
          "scanned_at_ms": "1797096190000",
          "offline": true,
          "revoked_device": false,
          "manual": false
        }
      ]
    }
  ],
  "revoked_device_passes": []
}
```

| Поле | Описание |
|---|---|
| `counter` | `total` — действующие билеты события, `passed` — сколько из них прошли. То же число «Прошло N / M» видит контролёр |
| `invalid_scans` | Сколько сканов закончились «недействителен»: чужой или отменённый билет, старый QR, подделка |
| `double_passes` | До 100 билетов, прошедших больше одного раза. Первый проход в `passes` настоящий, остальные двойные. Так бывает, когда два входа без интернета пропустили один билет |
| `revoked_device_passes` | До 100 билетов, настоящий проход которых пришёл с отозванного телефона. Гость уже вошёл, но такой проход стоит проверить |

У прохода: `scan_id`, `entrance` (имя устройства), `scanned_at_ms`, `offline` (без интернета), `manual` (контролёр пропустил вручную), `revoked_device` (с отозванного телефона).
