# Доступ, повторы и ограничения

Все пути относительно `https://ostrov.center/api/public/v1`.

| Операция | Доступ |
|---|---|
| GET center, catalog, objects, calendar, events, examples, openapi.json, skill.md | Без регистрации |
| POST quotes, suggestions | Без регистрации; расчёт без сохранения |
| POST sessions | Новая временная сессия без регистрации |
| POST estimates; GET estimates/{estimateId} | Bearer-сессия, свои документы |
| POST estimates/{estimateId}/edit | Свой черновик, актуальные версии, просьба человека |
| POST estimates/{estimateId}/applications | Своя смета, актуальные версии, просьба и согласие |
| GET applications/{applicationId} | Заявка своей сессии |
| POST sessions/revoke | Отзыв собственной сессии |

Сессия действует 30 дней. Сервер хранит хеш токена. Cookie или совпавшие имя/email не дают прав на документ. Токен передаётся только `Authorization: Bearer …`, не в query. CORS разрешает внешние клиенты без cookie-credentials. Полномочий сотрудника, подтверждения брони и платежей нет.

Человеческая ссылка — отдельный секрет доступа к одному документу и обычным действиям сайта. По ней могут быть доступны введённый alias/комментарии; черновик можно редактировать и отправить как заявку. Истечение или отзыв API-сессии не отзывает эту ссылку. Не публикуйте её и не отправляйте в аналитику. При продолжении оформления сайт может запросить недостающий контакт: не заполняйте его фиктивным значением.

Один `requestKey` длиной 16–120 символов (`A-Z a-z 0-9 . _ : -`) — для одной записи. Точный повтор в той же действующей сессии возвращает прежний результат; другое тело с прежним ключом даёт 409. После сетевой неопределённости разрешён ручной точный повтор тем же ключом, даже если ID ещё не получен. Автоматических повторов нет. Сверяйте состояние отдельным GET: ответ повтора может описывать прежнее состояние. Удалённые документы повтором не восстанавливаются.

Технические пределы — не коммерческие условия:

| Область | Предел |
|---|---|
| JSON-тело | 65 536 байт |
| Чтение на IP | 300 за 60 секунд |
| Расчёт/подбор на IP | 180 за 60 секунд |
| Новые сессии на IP | 20 за 3600 секунд |
| Новые записи сметы на IP | 20 за 3600 секунд |
| Заявки на IP | 5 за 600 секунд |
| Документы / успешные записи в сессии | 20 / 100 |
| Сессии / записи повторов во всём API | 1000 / 5000 |
| Сохранённые публичным API сметы | 1000 за всю историю |

Новая публичная смета также приостанавливается при 8000 клиентских карточек центра для сохранения ресурса другим каналам. При создании сессии сервер очищает не более одной истёкшей сессии и до 100 её записей повторов; сметы и заявки не удаляет. Точный повтор принятой записи доступен при заполненных квотах, пока сессия действует и документ доступен. Ограничения уменьшают случайный разгон запросов и базовые злоупотребления; не защищают от всех распределённых атак.

Ошибки: 400 неверные поля; 401 сессия отсутствует/истекла; 403 сохранение отключено; 404 неизвестный или чужой документ; 409 версия/ключ; 413 размер тела; 415 нужен JSON; 429 частота (`Retry-After` в секундах); 503 источник/ёмкость недоступны. Не интерпретируйте отказ как нулевую цену или наличие мест. После двух неуспешных обращений остановите самостоятельные попытки и объясните человеку следующий шаг.

Календарь: `start` включительно, `end` запроса исключительно; даты окончания события и дни присутствия резидентов включительны, зона Europe/Moscow. Ночи расчёта = выезд минус заезд. `kind=program` — опубликованная программа, `kind=occupancy` — занятость площадки, `kind=demo` — демонстрация. `residents`/`demoResidents` — агрегированные числа, не список гостей.

В публичном групповом quote отсутствует legacy `teamSettings`; `childSettings` содержит только возрастные правила и детскую скидку. Прочие резидентские условия не являются условиями группового тарифа. Денежные строки и итог совпадают с расчётным ядром; нормализация справочных полей не пересчитывает сохранённые сметы. При повторе старого запроса возвращается тот же сохранённый результат с актуальным публичным набором справочных полей. Формат `lines` различается по `kind`; итог всегда берите из `quote.total`, порядок объяснения — в SKILL.
