# HTTP API v1

Все пути ниже относительно BASE_URL. Публичный BASE_URL:
`https://data.fit.ippolitov.me`. Локальный: `http://127.0.0.1:8080`.

Описание по стандарту **OpenAPI 3.1.0** доступно по `GET /openapi.json`.
Эта спецификация включает схемы документов и карточек, параметры, заголовки
сценариев и ответы с ошибками. Она использует текущий origin (`servers: /`),
поэтому подходит для публикации в корне отдельного домена без привязки к
предметной области набора. Текстовые контракты: `GET /api.md`, `GET /data.md`.
Swagger UI / ReDoc в сервер не встроены; спецификацию можно открыть внешним
совместимым просмотрщиком.

| GET | Ответ |
| --- | --- |
| `/healthz` | `{status, snapshot}`; без сценариев и задержек |
| `/v1/datasets` | `{items: [manifest, ...]}` |
| `/v1/datasets/{dataset}/search?q=...&limit=10&tag=...` | `{items: [{id,title,score}, ...]}` |
| `/v1/datasets/{dataset}/documents/{id}` | `{id,title,language,aliases,tags,sections,source}`; без `links` |
| `/v1/datasets/{dataset}/documents/{id}/links?limit=5` | `{items: [{id}, ...]}` |
| `/v1/datasets/{dataset}/cards/{id}` | `{id,title,language,tags,excerpt,source}` |

`excerpt`: первые 600 Unicode-символов текста и `…`, если текст усечён.
Список links содержит только существующие цели в том же наборе, без повторов
и ссылки на себя, в исходном порядке. Лимит links: 1–20, search: 1–50.
Пагинации нет. Пустой результат — `200` и `items: []`.

`q`: непустая строка до 512 байт UTF-8; анализируются первые 20 слов.
Точное совпадение псевдонима/ID имеет приоритет. Полнотекстовый поиск требует
совпадения всех слов в заголовке или тексте. Фильтр `tag` — точное совпадение.
Значения ID и параметры запроса нужно URL-кодировать стандартной библиотекой.

Прикладные ошибки имеют вид
`{"error":{"code":"document_not_found","message":"unknown document"}}`.
Некорректные параметры — 400, неизвестные набор/документ — 404,
ошибка поиска — 500, перегрузка — 503 с `Retry-After: 1`.
Стандартные ответы HTTP-маршрутизатора на неизвестный путь/метод могут быть
plain text. Тексты документов всегда обычный текст, не HTML.

## Заголовки сценариев

- `X-Lab-Scenario: normal` — по умолчанию; случайное ожидание 0–300 мс.
- `X-Lab-Scenario: partial` — сценарий частичного отказа.
- `X-Lab-Session` — обязательный для partial идентификатор, например UUID v4.
  16–128 ASCII-букв, цифр, `_` и `-`. Новый ID создаётся на каждый поиск,
  сохраняется при выборе результата и при повторных попытках. Его передают
  во всех запросах этой операции.

Искусственные отказы применяются ко всем четырём основным методам: поиск,
список связей, полный документ и карточка. Список наборов, документация и
health не получают искусственных отказов. Без дополнительных настроек
эффективный seed равен `X-Lab-Session`, поэтому новый поиск меняет распределение.
На первом GET каждого ресурса в сессии применяется
`uint32_big_endian(SHA256(seed + "\n" + resource)[0:4]) % 4`:

| Значение | Поведение |
| --- | --- |
| 0 | Обычный успех |
| 1 | 503 `temporary_unavailable` |
| 2 | 429 `rate_limited`, `Retry-After: 1` |
| 3 | Ожидание 5 секунд, затем обычный ответ; проверка тайм-аута клиента |

Для 503/429 сохраняется обычная задержка до 300 мс. При втором и последующих
GET того же ресурса в сессии искусственный отказ не применяется. Документ и его
карточка — разные ресурсы. Для поиска и списка связей `resource` включает
декодированный путь и query string, канонизированный стандартным `url.Values.Encode`;
при отсутствии параметров используется только путь. Разные поисковые строки
считаются разными запросами, перестановка параметров не создаёт новую попытку.
Для полного документа и карточки `resource` равен декодированному пути,
поскольку их представление не зависит от query. Счётчик изменяется атомарно **при приёме
запроса**, до ожидания: клиентская отмена по тайм-ауту не обнуляет попытку.
Конкурентные дубликаты тоже считаются отдельными попытками.

Валидный UUID не создаёт авторизацию. Состояние локально одному процессу,
не переживает перезапуск и истекает через 30 минут без обращений к faultable
ресурсам. Максимум 1024 сессии / 256 ресурсов на сессию. При переполнении,
исчерпании лимита или смене seed сервер вернёт 409 `session_unavailable` с
описанием; при полном лимите нужно дождаться освобождения сессий, а не
создавать новые. Не запускать несколько независимых реплик без общего
хранилища сессий или закрепления сессии за процессом.

Ответ с задержкой — имитация медленного сервиса, а не HTTP 408/504. Тайм-аут
1 секунду задаёт клиент. Повторы должны быть ограничены; ожидание Retry-After
и backoff не должно блокировать поток. В первой версии сервер гарантирует
отсутствие повторного искусственного отказа внутри действующей сессии,
но реальные сетевые/серверные ошибки всё равно возможны.

В ответах есть `X-Request-ID`, `X-Catalog-Snapshot`, `Cache-Control: no-store`.
CORS разрешает GET/OPTIONS, три заголовка сценариев и чтение Retry-After.

## Необязательная настройка воспроизводимости

`X-Lab-Seed` — строка до 128 байт. Если она задана, она заменяет UUID как seed
распределения ошибок. Одинаковые seed и URL дают одинаковый первый исход
в разных новых сессиях. Внутри сессии эффективный seed менять нельзя.
Для обычной работы клиента и прохождения задания этот заголовок не нужен.

## Браузер каталога

`GET /` открывает встроенный интерфейс. Главная получает список коллекций;
форма поиска ведёт на отдельную страницу результатов. Карточка, полный текст
и связи открываются отдельными страницами. Каждый экран данных выполняет
ровно один запрос своего метода API; страница пустой формы не обращается к API.
Связи показываются идентификаторами без подгрузки карточек. Интерфейс работает
в обычном сценарии, без автоматических повторов и предварительной загрузки.
Тексты отображаются как текст; атрибуция, уведомления и лицензия сохраняются.
Исходники интерфейса — `web/`, встраиваются в бинарный файл; внешних ресурсов нет.
