07-api-reference

07. HTTP API reference

Базовый URL в dev: http://127.0.0.1:55400.
Авторизация: Authorization: Bearer <access_token> (кроме /auth/login и webhook-эндпоинтов).

Токен получать через POST /auth/login. Живёт 15 минут; refresh автоматически на фронте.


/auth

POST /auth/login

Вход.

{"email": "school_admin@local", "password": "change_me_locally"}

Ответ:

{"access_token": "eyJ...", "refresh_token": "eyJ...",
 "user": {"id": "...", "email": "...", "is_platform_admin": false,
          "memberships": [{"school_id": "...", "role_code": "school_admin"}]}}

POST /auth/refresh

Обновить access-токен.

{"refresh_token": "eyJ..."}

POST /auth/logout

Завершить сессию. Единственный способ разлогиниться.


/demo (dev-sanity)

GET /demo/visible-schools

Все школы, которые видит текущий пользователь по RLS. Для platform_admin — все; для остальных — своя (одна).


/courses

GET /courses

Каталог курсов школы текущего пользователя: {drafts: [...], published: [...]}.

GET /courses/{id}

Курс + последняя опубликованная версия + список всех версий.

GET /courses/{id}/v/{version}

Конкретная историческая версия (read-only).

POST /courses/{id}/copy

Deep-copy курса в ту же школу. Platform_admin обязан передать X-School-Id.

{"new_course_id": "clone-001", "new_title": "Клон"}

Коды: 201 success / 400 X-School-Id required / 404 not found / 409 id taken / 422 invalid.

PUT /courses/{id}/draft

Autosave черновика (teacher / school_admin). Тело — полная JSON-структура draft'а.


/cost

GET /cost/report?days=30

LLM-затраты. RLS автоматически фильтрует по школе (кроме platform_admin — видит всё).


/code

POST /code/run

Выполнить код в Judge0.

{"language": "python", "source": "print(42)", "stdin": ""}

Ответ: {stdout, stderr, time_ms, status}.


/media

POST /media/image

Сгенерировать изображение через Nano Banana.

{"prompt": "A neural network diagram, minimalist, watercolor"}

В dev при LMS_TEST_MODE=1 — cap 3 вызова на тест.

POST /media/audio

Gemini TTS / Lyria. В test-mode — заблокировано.

POST /media/video

Veo. В test-mode — заблокировано.

Все медиа кладутся в MinIO бакет media-assets и регистрируются в media_assetscost_usd).


/billing

GET /billing/providers

Список сконфигурированных провайдеров школы (или всех для platform_admin).

POST /billing/checkout

Создать checkout-сессию.

{"provider": "stripe", "sku": "plan_basic"}

Возвращает {checkout_url, session_id}. В dev checkout_url ведёт на /billing/mock-checkout.

POST /billing/webhooks/{provider}

Принимает события от провайдера. Без авторизации (подпись по body). Идемпотентно: повторный event возвращает {status:"duplicate", ack:true}. Используется superuser-движок → обходит RLS.


/learner (Phase 5, ADR-0015)

POST /learner/events

Записать событие обучения.

{"course_id": "demo-course",
 "course_version": 1,
 "block_path": "modules[0].lectures[0].blocks[0]",
 "block_id": null,
 "event_type": "view",
 "payload": {}}

event_type ∈ {view, attempt, pass, fail, complete, skip}.
201{id, occurred_at}. Пишется через RLS-движок: student пишет только о себе, school_admin — о любом в своей школе.

GET /learner/analytics/{course_id}

Агрегация по курсу. Доступ: school_admin / teacher / platform_admin (student → 403 analytics_forbidden_for_students).

Ответ:

{
 "course_id": "demo-course",
 "events_total": 120,
 "learners_total": 8,
 "completes": 5,
 "fails": 12,
 "attempts": 40,
 "completion_rate": 0.625,
 "hardest_blocks": [
    {"block_path": "modules[0].lectures[1].blocks[2]",
     "attempts": 10, "fails": 7, "fail_rate": 0.7}
 ]
}

/adaptive (Phase 5, ADR-0015)

POST /adaptive/supplement

Попросить адаптивный блок-подсказку.

{"course_id": "demo-course",
 "course_version": 1,
 "source_block_path": "modules[0].lectures[0].blocks[0]",
 "source_block_type": "quiz_single",
 "source_block_content": { ... },
 "recent_events": [
    {"event_type": "fail", "occurred_at": "..."}
 ],
 "language": "ru"}

Поведение:

Ответ:

{
 "block_id": "...",
 "block_type": "text",
 "block_content": { ... },
 "cached": false,
 "model": "gemini-3.1-flash-preview",
 "cost_usd": 0.00012
}

/health, /ready

GET /health

Liveness. 200 {"status":"ok"}.

GET /ready

Readiness (проверяет БД, MinIO, Temporal). В dev обычно 200; 503 если что-то лежит.


Стандартные ошибки

Код Когда
400 Ошибка валидации на уровне роутера (например, X-School-Id required)
401 Нет/плохой JWT
403 Прошла аутентификация, но нет прав (RLS не пустил / роль не та)
404 Ресурс не найден или невидим (RLS)
409 Конфликт (дубликат id курса)
422 Pydantic-валидация тела запроса
429 Rate limit / бюджет / adaptive cap
500 Серверная (в логах)
503 /ready говорит что сервис не готов

Как посмотреть полный актуальный список эндпоинтов

# OpenAPI JSON
curl http://127.0.0.1:55400/openapi.json | .venv/bin/python -m json.tool | less

# Swagger UI
open http://127.0.0.1:55400/docs

OpenAPI — источник правды. Это руководство может отставать на пару версий.