21 KiB
API конференций
Полная справка по эндпоинтам динамических конференций (см. ADR-001). Конференции создаются мгновенно или планируются; вместо предустановленных комнат и бронирований.
Обзор
Конференции — это постоянные сущности (номер, ссылка, владелец). Каждый запуск создаёт сеанс (ConferenceSession) — единицу AI-пайплайна.
- Номер: 9 десятичных цифр, уникален, генерируется с retry
- Ссылка:
slug = secrets.token_urlsafe(8)(11 base64url-символов), URL:/j/{slug} - Доступ: По номеру или ссылке (публичные эндпоинты без auth, rate limit 10/мин)
- Гости: Представляются при входе (имя обязательно, email факультативен для саммари)
Эндпоинты
POST /api/v1/conferences
Создать конференцию.
Без scheduled_at → мгновенная (создатель входит сразу); с scheduled_at → плановая.
Требует auth: JWT Bearer token
Request body:
{
"title": "Встреча Q3",
"scheduled_at": "2026-07-20T14:00:00Z",
"duration_minutes": 60,
"is_pinned": true,
"recurrence": {
"type": "weekly",
"weekdays": [0, 2, 4],
"time_local": "14:00",
"timezone": "Europe/Moscow",
"anchor_date": "2026-07-20",
"duration_minutes": 60
},
"is_closed": false,
"password": null,
"summary_recipients": null
}
Поля:
title(str, опционально, ≤255 символов) — название конференцииscheduled_at(ISO 8601 UTC, опционально) — время запуска; если пусто → мгновенная (status=active)duration_minutes(int, опционально, >0) — ожидаемая длительность (не обязывает)is_pinned(bool, опционально, default=false) — закреплённая ли (видна в "Моих конференциях")recurrence(RecurrenceRule, опционально) — только если is_pinned=true; см. нижеis_closed(bool, опционально, default=false) — требуется пароль для входаpassword(str, опционально, ≥4 символа) — обязателен если is_closed=truesummary_recipients(str, опционально:'all','owner', или null) — переопределение рассылки саммари конкретной конференции; null = использовать дефолт инстансаparticipants(array, опционально) — список приглашённых; каждый элемент:{"user_id": "uuid" или null, "email": "string" или null}. Организатор добавляется автоматически (не требуется в массиве, но если передан — дедуплицируется). Внешние email'ы приглашаются отдельно
RecurrenceRule (JSONB в БД):
{
"type": "weekly|biweekly|monthly|every_n_days",
"weekdays": [0, 1, 2, 3, 4, 5, 6],
"day_of_month": null,
"interval_days": null,
"anchor_date": "2026-07-20",
"time_local": "HH:MM",
"timezone": "IANA (e.g. Europe/Moscow)",
"duration_minutes": 60
}
- weekly:
weekdaysобязателен (0=пн, 6=вс), список непустой - biweekly:
weekdaysобязателен, повтор через 2 недели от anchor_date - monthly:
day_of_monthобязателен (1..31; 31 → последний день короткого месяца) - every_n_days:
interval_daysобязателен (≥1)
Response (201 Created):
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"number": "123456789",
"slug": "abc123456",
"title": "Встреча Q3",
"status": "active|scheduled|ended",
"is_pinned": true,
"is_closed": false,
"scheduled_at": "2026-07-20T14:00:00Z",
"duration_minutes": 60,
"recurrence": { /* RecurrenceRule */ },
"summary_recipients": null,
"next_occurrence": "2026-07-22T14:00:00Z",
"created_at": "2026-07-16T12:00:00Z",
"join": {
"livekit_url": "wss://livekit.example.com",
"token": "eyJh...",
"room_name": "abc123456",
"conference_id": "550e8400-e29b-41d4-a716-446655440000"
}
}
Мгновенная конференция: status=active, join заполнено. Плановая: status=scheduled, join=null.
Коды ошибок:
422—closed_conference_requires_password(is_closed но нет password)422—recurrence_requires_pinned(recurrence но is_pinned=false)422—scheduled_at_in_the_past(дата в прошлом)
GET /api/v1/conferences/my
Мои конференции: закреплённые + предстоящие разовые владельца ИЛИ приглашённого.
С 2026-07-20 (решение поверх ADR-003) в выдачу попадает конференция, если текущий пользователь:
- владелец (
owner_id == user.id), ИЛИ - приглашён по
user_id(строкаconference_inviteesсuser_id == user.id), ИЛИ - приглашён по email (строка
conference_inviteesсlower(email) == lower(email пользователя)— внешнее приглашение на адрес, под которым человек впоследствии зарегистрировался).
Критерии показа не меняются (закреплённая — безусловно; разовая — status=scheduled
и scheduled_at в будущем), меняется только круг «чья» конференция. Для
приглашённого is_owner=false, organizer_name — имя фактического владельца;
participants не заполняется (как и для владельца — список не раздувает состав).
Требует auth: JWT Bearer token
Query parameters: нет
Response (200 OK):
[
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"number": "123456789",
"slug": "abc123456",
"title": "Встреча Q3",
"status": "scheduled",
"is_pinned": true,
"is_closed": false,
"scheduled_at": "2026-07-20T14:00:00Z",
"duration_minutes": 60,
"recurrence": { /* RecurrenceRule */ },
"summary_recipients": null,
"next_occurrence": "2026-07-22T14:00:00Z",
"created_at": "2026-07-16T12:00:00Z",
"join": null
}
]
GET /api/v1/conferences/calendar
Развёртка вхождений (occurrences) конференций в диапазоне дат — владельца ИЛИ приглашённого.
Тот же принцип видимости, что у GET /conferences/my (см. выше, решение от
2026-07-20): вхождения строятся по конференциям, где пользователь владелец
или приглашён (по user_id или по lower(email)).
Требует auth: JWT Bearer token
Query parameters:
from(ISO 8601 UTC) — начало диапазонаto(ISO 8601 UTC) — конец диапазона- Максимальная ширина: 62 дня; иначе 422
Response (200 OK):
[
{
"conference_id": "550e8400-e29b-41d4-a716-446655440000",
"title": "Встреча Q3",
"starts_at": "2026-07-20T14:00:00Z",
"ends_at": "2026-07-20T15:00:00Z",
"number": "123456789",
"slug": "abc123456",
"is_pinned": true,
"is_closed": false
}
]
GET /api/v1/conferences/resolve
Найти конференцию по номеру или ссылке — публичный эндпоинт без auth.
Rate limit: 10 запросов в минуту на IP
Query parameters:
q(str, ≥1 символ) — номер (9 цифр) или slug
Response (200 OK):
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"title": "Встреча Q3",
"status": "active|scheduled|ended",
"is_closed": false,
"requires_password": false
}
Коды ошибок:
404— не найдена (живая, мёртвая или несуществующая — единообразный ответ для безопасности)429— exceeded rate limit
POST /api/v1/conferences/{conference_id}/join
Вход зарегистрированного пользователя в конференцию.
Требует auth: JWT Bearer token
Path parameters:
conference_id(UUID)
Request body:
{
"password": "optional_password"
}
Response (200 OK):
{
"livekit_url": "wss://livekit.example.com",
"token": "eyJh...",
"room_name": "abc123456",
"conference_id": "550e8400-e29b-41d4-a716-446655440000"
}
Коды ошибок:
404— conference_not_found403— password_required (закрытая конференция без пароля в теле запроса)403— invalid_password410— conference_ended (статус=ended)
POST /api/v1/conferences/{conference_id}/guest-join
Вход гостем: представиться (имя обязательно) — публичный, без auth.
Rate limit: 10 запросов в минуту на IP
Path parameters:
conference_id(UUID)
Request body:
{
"display_name": "Иван Иванов",
"email": "ivan@example.com",
"password": "optional_password"
}
Поля:
display_name(str, 1..255) — обязателенemail(EmailStr, опционально) — для рассылки саммариpassword(str, опционально) — для закрытых конференций
Response (200 OK):
{
"livekit_url": "wss://livekit.example.com",
"token": "eyJh...",
"room_name": "abc123456",
"conference_id": "550e8400-e29b-41d4-a716-446655440000"
}
Коды ошибок:
404— conference_not_found403— password_required403— invalid_password410— conference_ended429— exceeded rate limit422— validation_error (display_name пуст, email некорректен)
PATCH /api/v1/conferences/{conference_id}
Изменить конференцию — только владелец или администратор.
Требует auth: JWT Bearer token
Path parameters:
conference_id(UUID)
Request body: все поля опциональны (как ConferenceCreateIn, но PATCH)
{
"title": "Новое название",
"scheduled_at": "2026-07-25T15:00:00Z",
"duration_minutes": 90,
"is_pinned": true,
"recurrence": null,
"is_closed": false,
"password": null
}
Response (200 OK): обновленная ConferenceOut
Коды ошибок:
404— conference_not_found403— not_owner422— invalid_conference_state (например, попытка добавить recurrence к неживой конференции)
DELETE /api/v1/conferences/{conference_id}
Удалить конференцию — только владелец или администратор.
Требует auth: JWT Bearer token
Path parameters:
conference_id(UUID)
Response (204 No Content) — без тела
Коды ошибок:
404— conference_not_found403— not_owner409— conference_active (активную конференцию нельзя удалить, дождитесь завершения)
GET /api/v1/conferences/{conference_id}
Получить детальную информацию о конференции (с полным составом участников) — владелец, администратор ИЛИ приглашённый.
Приглашённый определяется так же, как в GET /my/GET /calendar: строка
conference_invitees с user_id == user.id либо с lower(email) == lower(email пользователя). Это расширение касается ТОЛЬКО чтения — PATCH/DELETE
по-прежнему разрешены только владельцу или администратору (403 приглашённому).
Требует auth: JWT Bearer token
Path parameters:
conference_id(UUID)
Response (200 OK):
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"number": "123456789",
"slug": "abc123456",
"title": "Встреча Q3",
"status": "scheduled",
"is_pinned": true,
"is_closed": false,
"scheduled_at": "2026-07-20T14:00:00Z",
"duration_minutes": 60,
"recurrence": { "type": "weekly", "weekdays": [0, 2, 4] },
"summary_recipients": "all",
"owner_id": "550e8400-e29b-41d4-a716-446655440000",
"is_owner": true,
"organizer_name": "Иван Иванов",
"participants": [
{
"user_id": "550e8400-e29b-41d4-a716-446655440000",
"email": "ivan@example.com",
"name_user": "Иван Иванов",
"avatar_url": "/media/avatars/550e8400-e29b-41d4-a716-446655440000.jpg"
},
{
"user_id": null,
"email": "external@example.com",
"name_user": null,
"avatar_url": null
}
],
"created_at": "2026-07-16T12:00:00Z"
}
Поля:
owner_id— UUID владельца конференцииis_owner— true если текущий пользователь владелецorganizer_name— имя организатора (берётся изusers.name_user)participants— массив приглашённых (зарегистрированные и внешние). Заполняется только в этом эндпоинте; в GET /my и /calendar пустой массив.
Коды ошибок:
404— conference_not_found403— not_owner (пользователь не владелец, не администратор и не приглашён)
Статусы конференции
| Статус | Описание |
|---|---|
scheduled |
Плановая, время ещё не наступило или никто не вошёл |
active |
В настоящий момент идёт (кто-то вошёл или прошло scheduled_at) |
ended |
Завершена; истории, фразы, саммари остаются; вход возвращает 410 |
Переходы:
- Мгновенная создание →
activeсразу - Плановая создание →
scheduled - Webhook
room_started→active - Webhook
room_finished→ended(если не pinned) илиscheduled(если pinned) - Beat-задача →
endedесли истеклоscheduled_at + duration_minutesи никто не входил
LiveKit identity
Интеграция с LiveKit на уровне webhook'ов и токенов:
- Зарегистрированный:
identity = str(user_id),name = user.name_user - Гость:
identity = "guest:{guest_access.id}",name = guest_access.display_name
Email гостя в LiveKit не передаётся (PII не утекает участникам).
Ошибки и коды HTTP
| Код | Описание |
|---|---|
200 |
OK |
201 |
Created (POST конференции) |
204 |
No Content (DELETE) |
400 |
Bad Request (некорректный JSON) |
401 |
Unauthorized (JWT missing или invalid) |
403 |
Forbidden (password_required, invalid_password, not_owner) |
404 |
Not Found (конференция не существует или не найдена) |
409 |
Conflict (conference_active — нельзя удалить активную) |
410 |
Gone (conference_ended — вход в завершённую) |
422 |
Unprocessable Entity (валидация, invalid range на /calendar) |
429 |
Too Many Requests (rate limit на /resolve, /guest-join) |
Примеры запросов
Создать мгновенную конференцию
curl -X POST http://localhost:8000/api/v1/conferences \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"title":"Quick call"}'
Создать плановую с повторением (еженедельно по пн/ср/пт)
curl -X POST http://localhost:8000/api/v1/conferences \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"title": "Q3 standup",
"scheduled_at": "2026-07-21T09:00:00Z",
"duration_minutes": 30,
"is_pinned": true,
"is_closed": true,
"password": "secret123",
"recurrence": {
"type": "weekly",
"weekdays": [0, 2, 4],
"time_local": "09:00",
"timezone": "Europe/Moscow",
"anchor_date": "2026-07-21",
"duration_minutes": 30
}
}'
Вход по номеру (публичный, без auth)
curl http://localhost:8000/api/v1/conferences/resolve?q=123456789
Вход гостем
curl -X POST http://localhost:8000/api/v1/conferences/550e8400-e29b-41d4-a716-446655440000/guest-join \
-H "Content-Type: application/json" \
-d '{
"display_name": "Иван",
"email": "ivan@example.com",
"password": "secret123"
}'
Рассылка приглашений
При создании (POST) или изменении (PATCH) конференции отправляются .ics-приглашения:
Получатели:
- Организатор (владелец конференции, автоматически)
- Все приглашённые в
participants(зарегистрированные пользователи и внешние email'ы)
Содержимое письма:
- Тема:
"Приглашение: <название конференции>" - Вложение
.ics(iCalendar формат)METHOD:REQUEST— приглашениеVTIMEZONE— часовой пояс инстанса (для корректного отображения)RRULE— правило повтора (для закреплённых конференций сrecurrence)SEQUENCE— номер версии события
Логика рассылки:
- Создание (POST): Рассылка всем в
participantsи организатору.SEQUENCE=0. - Изменение расписания (PATCH
scheduled_at,duration_minutes,recurrence,title): ИнкрементируетсяSEQUENCE, рассылка повторяется. - Изменение только состава участников (PATCH
participants): Рассылка отправляется, ноSEQUENCEне меняется (календарные клиенты игнорируют обновления с неизменённым SEQUENCE). - Прочие изменения (пароль,
is_closed,summary_recipients): Рассылка не отправляется.
Внешние приглашённые:
- Письма отправляются на email'ы без требования аутентификации.
- При входе по ссылке гост вводит имя и опционально email.
- Если email гостя совпадает с приглашённым — он может получить саммари (зависит от
summary_recipients).
Примечания
- Все
datetimeсохраняются и возвращаются в UTC (ISO 8601, суффиксZ) - Преобразование в локальное время — на клиенте по таймзоне браузера
.icsэкспорт включаетVTIMEZONEдля правильной конвертации (services/ics.py)- Номер и slug неизменны всю жизнь конференции и не переиспользуются
recurrenceхранится как JSONB (можно запросить всё, развёртка только в памяти)
Ссылки
- Architecture Overview — система компонентов
- Database Schema — таблицы
conferences,conference_sessions,guest_access - ADR-001: Динамические конференции вместо бронирований — обоснование решения
- Recurrence Service — реализация развёртки