# Участники и доступ

## Свой профиль и активные входы

---

### 69. Изменить свой профиль

*Метод в API с 2026-09-08.*

Раньше профиль только читался (`GET /user`) — поменять было нельзя даже часовой
пояс, от которого зависят все даты в отчётах и выгрузках.

**HTTP:** `POST /user`

| Поле | Тип | Описание |
|---|---|---|
| `name` | string | 3–255 символов. Имя владельца попадает и в название его аккаунтов. |
| `lang` | string | Язык интерфейса и писем. |
| `timezone` | string | Часовой пояс, например `Asia/Novosibirsk`. |
| `theme` | string | `light`, `dark`, `auto`. |
| `subscribe_action` | boolean | Письма о событиях в опросах. |
| `subscribe_billing` | boolean | Письма о списаниях и тарифе. |
| `subscribe_marketing` | boolean | Рекламные письма. |

Передавать можно любое подмножество. **Подписки внутри записываются тройкой**, и
незаданные берутся из текущего состояния — отказ от рекламных писем не выключает
письма о списаниях.

Пустой запрос — `422` с кодом `nothing_to_update`.

Смены почты и пароля здесь нет: там код из письма и текущий пароль. Аватара тоже
— это загрузка картинки. Удаления аккаунта — тем более.

Если часть полей записалась, а часть нет — `400` с кодом `partially_saved`,
списком `failed` и фактическим состоянием в `profile`.

```json
{
    "status": true,
    "profile": {"name": "Иван Сидоров", "lang": "ru", "timezone": "Asia/Novosibirsk",
                "theme": "light", "subscribe_action": true, "subscribe_billing": true,
                "subscribe_marketing": false}
}
```

---

### 70. Активные входы в аккаунт

*Метод в API с 2026-09-08.*

Где человек ещё залогинен, с какого устройства и когда заходил.

**HTTP:** `GET /user/sessions`

Список входов ведётся только при хранении сессий в Redis. Если хранилище другое,
ответ — `400` с кодом `not_supported`, а не пустой список.

---

### 71. Закрыть вход

*Метод в API с 2026-09-08.*

**HTTP:** `POST /user/sessions/close`

| Поле | Тип | Описание |
|---|---|---|
| `session_id` | string | Закрыть один вход. |
| `confirm_close_all` | boolean | Обязателен, если `session_id` не передан. |

Без `session_id` закрываются **все** входы, включая текущий вход человека в
кабинет: у запроса по ключу своей сессии нет, и исключать нечего. Поэтому нужно
подтверждение — иначе владелец аккаунта неожиданно окажется разлогинен.

Закрыть можно только свой вход: `session_id` сверяется со списком входов
аккаунта. Неизвестный или чужой вход — `400` с кодом `session_not_found`.

---

## Участники аккаунта, роли и доступ к папкам

Приглашение, снятие, смена роли и правка ролей требуют право участника
**`member_crud`**; доступ к папке — право **`invite_members`** с учётом автора
папки (у прав есть значение «только свои»). Чтение состава и ролей — обычный
доступ к аккаунту.

Роль и папки всегда сверяются с этим же аккаунтом: чужая роль дала бы
приглашённому права из чужого набора, чужая папка — доступ к чужим опросам. Роль
владельца не назначается никому и не правится: она одна на аккаунт.

---

### 85. Участники аккаунта

*Метод в API с 2026-09-08.*

**HTTP:** `GET /workspace/{workspace_id}/members`

| Параметр | Тип | Описание |
|---|---|---|
| `status` | string | `all` (по умолчанию), `active`, `pending` — приглашённые, но не принявшие приглашение. |
| `limit` | integer | 1–500, по умолчанию 100. |
| `offset` | integer | |

У записи участника есть поле `active`: `false` означает, что приглашение ещё не
принято.

---

### 86. Пригласить участника

*Метод в API с 2026-09-08.*

**HTTP:** `POST /workspace/{workspace_id}/members`

| Поле | Тип | Обязательное | Описание |
|---|---|---|---|
| `email` | string | Да | Адрес приглашения. |
| `name` | string | Да | 3–250 символов. |
| `role_id` | integer | Да | Роль этого аккаунта, кроме роли владельца. |
| `folder_ids` | array | Нет | Папки аккаунта. **Пустой список означает доступ ко всем папкам**: без папок человек не увидел бы ни одного опроса. |
| `comment` | string | Нет | 3–250 символов. |

Тарифный лимит участников исчерпан — `403` с `blocked_by_tariff`. Чужая или
владельческая роль — `400` с кодом `role_not_found`, чужая папка — `400` с
кодом `folder_not_found`.

Адрес уже в аккаунте — `400` с кодом `member_already`. Это отдельный код, а не
общий `invite_failed`: повторное приглашение — обычный случай автоматизации, и
отличать его от настоящего сбоя записи нужно без разбора текста.

---

### 87. Снять участника, переслать приглашение

*Метод в API с 2026-09-08.*

| Действие | HTTP |
|---|---|
| Снять | `DELETE /workspace/{workspace_id}/members/{memberId}` |
| Переслать приглашение | `POST /workspace/{workspace_id}/members/{memberId}/resend-invite` |

---

### 88. Роль и папки участника

*Метод в API с 2026-09-08.*

| Действие | HTTP | Поле |
|---|---|---|
| Сменить роль | `POST /workspace/{workspace_id}/members/{memberId}/role` | `role_id` |
| Заменить набор папок | `POST /workspace/{workspace_id}/members/{memberId}/folders` | `folder_ids` |

Набор папок заменяется целиком, поэтому передавать его надо всегда — в том числе
пустым, если доступ снимают. Отсутствие поля даёт `422`.

---

### 89. Права одного участника

*Метод в API с 2026-09-08.*

**HTTP:** `GET /workspace/{workspace_id}/members/{memberId}/access`

Отдаёт разобранный набор прав: то же, чем руководствуются проверки доступа.
Участник другого аккаунта — `400` с кодом `member_not_found`.

---

### 90. Роли аккаунта

*Метод в API с 2026-09-08.*

| Действие | HTTP |
|---|---|
| Список с правами | `GET /workspace/{workspace_id}/roles` |
| Создать или изменить | `POST /workspace/{workspace_id}/roles` |
| Удалить | `DELETE /workspace/{workspace_id}/roles/{roleId}` |

Тело записи: `name` (3–250), `permissions` — список `{name, value}`, где
`value` это `allowed`, `forbidden` или `partially` («только свои опросы»). Без
`role_id` заводится новая роль, с ним правится существующая.

Новая роль с уже занятым именем не заводится — `400` с кодом `duplicate_name`:
в списке такие роли неразличимы, и «назначь роль Менеджер» становится
неоднозначным.

Правится и удаляется только роль этого аккаунта и не роль владельца: чужая роль
— `400`. Носители удалённой роли переводятся на роль `member` того же аккаунта.

**Права по папкам.** Необязательное поле `folder_permissions` задаёт отличия для
отдельных папок: список записей `{folder_id, permissions}`, где `permissions` —
это «имя права → значение». Поле не передали — отличия не меняются, пустой массив
`[]` снимает все, папка чужого аккаунта отбрасывается молча. В ответе списка
ролей рядом с `folder_id` приходит `name` папки; удалённых папок в ответе не
бывает.

По папкам настраиваются `create_quiz`, `edit_quiz`, `rename_quiz`, `copy_quiz`,
`delete_quiz`, `answers_delete`, `view_results`, `settings`, `integrations`,
`copy_in_folder`, `move_in_folder`, `rename_folder`, `delete_folder`. Остальные
права описывают аккаунт целиком, значение по папке для них сохранится, но
применяться не будет.

---

### 91. Доступ участников к папке

*Метод в API с 2026-09-08.*

**HTTP:** `POST /workspace/{workspace_id}/folders/{folderId}/access`

| Поле | Тип | Обязательное | Описание |
|---|---|---|---|
| `action` | string | Да | `members` — состав, `add`, `remove`. |
| `member_id` | integer | Кроме `members` | |

Отказы приходят кодом: `member_not_found`, `member_already_in_folder`,
`member_not_in_folder`, `self_remove` (у себя доступ снять нельзя — участник
остался бы без единственной папки).

---