# Посещения и публичные ссылки

## Посещения опроса

### 128. Посещения по источникам, устройствам, браузерам и странам

*Метод в API с 2026-10-13.*

Разбивает посещения опроса по одному признаку: значению параметра ссылки (`utm_source`, `utm_campaign`, любая скрытая переменная), устройству, браузеру или стране.

**HTTP:** `GET /quiz/{id}/summary/segments`

**Право:** просмотр результатов опроса.

| Параметр | Обязательный | Значения |
|---|---|---|
| `by` | да | `param`, `device`, `browser`, `country` |
| `key` | при `by=param` | имя параметра ссылки, до 255 символов |
| `date_from`, `date_to` | нет | `YYYY-MM-DD`, включительно. Период — по дню посещения в часовом поясе владельца опроса |
| `device` | нет | `desktop`, `mobile`, `tablet` |

```
GET /quiz/18197/summary/segments?by=param&key=utm_source&date_from=2026-09-14&date_to=2026-10-13
```

#### Пример ответа (200)

```json
{
    "status": true,
    "rows": [
        { "value": "email", "opens": 230, "unique_browsers": 223, "with_answer": 152, "completed": 111 },
        { "value": "", "opens": 3, "unique_browsers": 3, "with_answer": 1, "completed": 0 },
        { "value": null, "opens": 24, "unique_browsers": 24, "with_answer": 18, "completed": 14 }
    ],
    "truncated": false
}
```

| Поле | Описание |
|---|---|
| `rows[].value` | Значение признака строкой. У `by=param`: `null` — посещения без этого параметра, `""` — параметр был пустым (отдельная строка). У устройства, браузера и страны `null` — значение неизвестно или скрыто анонимностью |
| `rows[].opens` | Посещений |
| `rows[].unique_browsers` | Уникальных браузеров среди них. Суммы по строкам пересекаются |
| `rows[].with_answer` | Посещений хотя бы с одним ответом (не скрытым, не на модерации, не исключённым) |
| `rows[].completed` | Посещений с завершённым ответом |
| `rows[].code` | Только при `by=country`: код ISO 3166-1 alpha-2 страны строки, `null` — не определена или не из справочника |
| `truncated` | `true` — строк было больше, чем пришло |

Порядок — по `opens` от большего, строка `null` всегда последняя. Строк не больше 200, строка `null` входит в это число. Если в опросе включены анонимные ответы, разбивка по устройству, браузеру и стране приходит пустой; по параметру ссылки — как обычно.

#### Ошибки
| Код | Описание |
|-----|----------|
| 400 | Опрос не найден |
| 401 | Не авторизован |
| 403 | Нет доступа к опросу или права на результаты |
| 422 | `validation_failed`: нет `by` или неизвестный `by`, нет `key` при `by=param`, плохие даты (не `YYYY-MM-DD`, `date_from` позже `date_to`), неизвестное устройство |
| 503 | `query_timeout`: на очень крупном опросе подсчёт не уложился в 20 секунд — сузьте период или выберите устройство |

### 129. Журнал посещений

*Метод в API с 2026-10-13.*

Посещения опроса, сначала новые: когда, на чём, с какими параметрами ссылки и что с посещением стало.

**HTTP:** `GET /quiz/{id}/summary/visits`

**Право:** просмотр результатов опроса.

| Параметр | Значения | Описание |
|---|---|---|
| `status` | `all` (по умолчанию), `no_answer`, `started`, `completed`, `unfinished`, `screenout` | `started` — все посещения с ответом |
| `param_key` + `param_value` | имя до 255 символов, значение до 2000 | Только с этим значением параметра ссылки; пустое значение — параметр был пустым |
| `param_key` + `param_missing=1` | | Только пришедшие без параметра |
| `segment_by` + `segment_value` | `device`, `browser`, `country` и значение до 255 символов | Только с этим устройством, браузером или страной — в том виде, в каком их отдаёт §128 |
| `segment_by` + `segment_unknown=1` | | Только с неизвестным значением признака |
| `date_from`, `date_to`, `device` | как в §128 | |
| `limit` | 1–100, по умолчанию 30 | |
| `offset` | 0–1000000, по умолчанию 0 | |

```
GET /quiz/18197/summary/visits?status=no_answer&segment_by=device&segment_value=mobile
```

#### Пример ответа (200)

```json
{
    "status": true,
    "total": 230,
    "counts": { "all": 230, "no_answer": 78, "started": 152, "completed": 111, "unfinished": 31, "screenout": 10 },
    "items": [
        {
            "id": 99102,
            "created_at": "2026-10-13T20:57:00+03:00",
            "device": "desktop",
            "browser": "Chrome",
            "country": "Russia",
            "params": { "utm_source": "email", "utm_medium": "social" },
            "status": "completed",
            "answer_id": 55120
        }
    ]
}
```

| Поле | Описание |
|---|---|
| `total` | Посещений под всеми фильтрами, включая `status` |
| `counts` | По статусам под теми же фильтрами без учёта `status` |
| `items[].id` | Посещение |
| `items[].created_at` | Время посещения, ISO 8601, в часовом поясе владельца опроса |
| `items[].device`, `browser`, `country` | `null` — неизвестно или скрыто анонимностью опроса |
| `items[].country_code` | Код ISO 3166-1 alpha-2 страны посещения; `null` — не определена, не из справочника или скрыта |
| `items[].params` | Параметры ссылки, с которыми пришло посещение, значения строкой; не пришедших параметров в объекте нет |
| `items[].status` | По лучшему ответу посещения: `completed` > `screenout` > `unfinished`; нет ответа — `no_answer` |
| `items[].answer_id` | Ответ, задавший статус; `null` — ответа нет. На тарифе без незавершённых ответов у `unfinished` и `screenout` — тоже `null`: такие ответы там не открываются |

#### Ошибки
| Код | Описание |
|-----|----------|
| 400 | Опрос не найден |
| 401 | Не авторизован |
| 403 | Нет доступа к опросу или права на результаты |
| 422 | `validation_failed`: неизвестный `status`; `param_key` без `param_value` и без `param_missing`, или наоборот; `param_value` вместе с `param_missing=1`; то же у `segment_by` / `segment_value` / `segment_unknown`; `limit` вне 1–100; `offset` вне 0–1000000; плохие даты или устройство |
| 503 | `query_timeout`: как в §128 |

## Публичные ссылки на результаты

### 130. Список публичных ссылок раздела

*Метод в API с 2026-10-13.*

Самые новые ссылки на сводку, отчёт или ответы опроса — вместе с выключенными.
Нужно право участника **`view_results`**.

**HTTP:** `GET /quiz/{id}/shared/{section}`

`section` — `summary`, `report` или `answers`. Другое значение — 404.

| Параметр | Тип | Обязательный | Описание |
|---|---|---|---|
| `limit` | integer | Нет | Сколько самых новых ссылок вернуть, от 1 до 100. По умолчанию 100 |

#### Пример ответа (200)
```json
{
    "status": true,
    "total": 3,
    "items": [
        {
            "uuid": "9f1c2e7a-4b6d-3c8e-a1b2-c3d4e5f60718",
            "number": 3,
            "url": "https://api.webask.io/b33f5he82/answers/9f1c2e7a-4b6d-3c8e-a1b2-c3d4e5f60718",
            "created_at": "2026-10-13T01:02:00+03:00",
            "is_active": true,
            "answers_count": 2
        }
    ]
}
```

| Поле | Описание |
|---|---|
| `total` | Сколько всего ссылок у раздела опроса, вместе с выключенными |
| `items` | Ссылки, сначала новые, не больше `limit` |
| `uuid` | Идентификатор ссылки для §131 |
| `number` | Номер по порядку создания среди ссылок раздела опроса, с 1. Выключенные тоже считаются, номера не сдвигаются |
| `url` | Полный адрес публичной страницы |
| `created_at` | Когда создана, ISO 8601 в часовом поясе пользователя ключа |
| `is_active` | `false` — ссылка выключена (§131) |
| `answers_count` | Только у `answers`: сколько ответов в ссылке на выбранные ответы; `null` — ссылка на все ответы |

В список `answers` входят ссылки на все ответы и на выбранные ответы. Ссылки
респондентов на свой ответ со страницы завершения и ссылки «все, кроме
выбранных» (`type: not_in`) не входят и не считаются в `total` и `number`.

#### Ошибки
| Код | Описание |
|-----|----------|
| 400 | Опрос не найден (`code: quiz_not_found`) |
| 403 | Нет доступа к опросу или права `view_results` |
| 404 | Неизвестный `section` |
| 422 | `limit` не число от 1 до 100 |

### 131. Выключить или включить публичную ссылку

*Метод в API с 2026-10-13.*

Выключенная ссылка перестаёт открываться: публичная страница отвечает так же,
как на ссылку, которой нет, данные результатов не отдаются. Ссылка не
удаляется — остаётся в списке §130 с `is_active: false`, её можно включить
обратно. Нужно право участника **`view_results`**.

**HTTP:** `PATCH /quiz/{id}/shared/{section}/{uuid}`

#### Тело запроса
```json
{ "is_active": false }
```

| Поле | Тип | Обязательное | Описание |
|---|---|---|---|
| `is_active` | boolean | Да | `false` — выключить, `true` — включить. Только `true` или `false`: `0`, `1`, `"false"` — ошибка 422 |

Повторный вызов с тем же значением ничего не меняет и отвечает 200.

#### Пример ответа (200)
```json
{
    "status": true,
    "link": {
        "uuid": "9f1c2e7a-4b6d-3c8e-a1b2-c3d4e5f60718",
        "number": 3,
        "url": "https://api.webask.io/b33f5he82/summary/9f1c2e7a-4b6d-3c8e-a1b2-c3d4e5f60718",
        "created_at": "2026-10-13T01:02:00+03:00",
        "is_active": false
    }
}
```

`link` — ссылка в том же виде, что в списке §130.

#### Ошибки
| Код | Описание |
|-----|----------|
| 400 | Опрос не найден (`code: quiz_not_found`) |
| 403 | Нет доступа к опросу или права `view_results` |
| 404 | У раздела опроса нет ссылки с таким `uuid` (`code: share_link_not_found`), или неизвестный `section` |
| 422 | `is_active` не `true` и не `false` |