# OctoFunnel — состав клиентского API v1

Дата исследования: 8 ноября 2026. Статус: функциональный состав согласован пользователем и расширен созданием/редактированием сайтов, страниц, графов воронок, курсов и уроков. Методы, схемы и технические детали — проект контракта, не документация работающего публичного API.

Аудитория: клиенты платформы и разработчики их интеграций. Один ключ предоставляет доступ к одному проекту. Партнёрское управление чужими проектами и MCP остаются отдельными поверхностями.

## Результат исследования

В платформе уже есть внутренние HTTP-операции для CRM, воронок, курсов, товаров, тарифов, покупок, подписок, переписки, медиа, рассылок и других областей. Изученные маршруты админки используют сессии и доменные права. Это основа для повторного использования бизнес-логики, но не готовый публичный контракт.

В проверенных декларациях маршрутов не найден отдельный версионированный клиентский API с проектными API-ключами и каталогом исходящих событий для интеграторов. Найденные входящие webhook-маршруты обслуживают провайдеров и мессенджеры; они не подтверждают наличие системы подписок клиента на события.

Расширенный v1: **81 HTTP-операция в 10 функциональных группах** (первоначальная 41 операция и 40 новых). Все URL ниже — предложение нового контракта, а не существующие URL. Число операций не является оценкой трудозатрат.

Приоритет — следующие сценарии:

1. Форма на стороннем сайте → контакт → тег → подключение к существующей воронке.
2. Внешняя CRM ↔ контакты, поля, переменные и этапы воронок.
3. Внешняя оплата → выдача ручного доступа к курсу. Такой доступ не создаёт покупку и не доказывает факт оплаты.
4. Покупка на платформе → событие в сторонней CRM или системе учёта.
5. Входящее сообщение → внешняя обработка → ответ через уже подключённый канал.
6. Интеграция создаёт сайт и страницы, заполняет их блоками и обновляет существующий контент с проверкой ревизии.
7. Интеграция создаёт воронку и редактирует её граф, сохраняя совместимость с визуальным редактором.
8. Внешняя система создаёт курс, модули и уроки, задаёт порядок и содержимое, затем использует ранее включённые методы выдачи доступа.

## Правила контракта

- Предлагаемый префикс: `/api/v1`. Точный публичный origin определяется перед выпуском.
- Авторизация: `Authorization: Bearer <project-api-key>`. Ключи создаются, отзываются и перевыпускаются в настройках проекта. Самообслуживание ключей через API не входит в v1.
- Проект и владелец разрешаются сервером по ключу. Передача `projectId` в теле или query не выбирает другой проект.
- У ключа — явные разрешения. Выдача доступов и отправка сообщений требуют отдельных разрешений; ключ не расширяет возможности своего владельца и тарифного плана.
- Наружу выдаются стабильные непрозрачные ссылки на сущности. Не публиковать внутренние числовые DB id, provider tokens, MCP credentials или служебные настройки.
- Схемы запросов и ответов — в `@octo/contracts`. API, админка и MCP используют канонические доменные операции и данные, без копий CRM, заказов или прав доступа.
- Для списков — ограниченный `limit`, непрозрачный `cursor`, `next_cursor`. Параметры фильтрации перечисляются отдельно для каждого ресурса.
- Даты — RFC 3339 с часовым поясом. Деньги — целое число в минимальных единицах плюс валюта. Это формат выдачи API, не требование менять внутреннее хранение.
- Для команд POST с побочными эффектами — `Idempotency-Key`: одинаковый ключ и тело возвращают сохранённый результат, отличающееся тело даёт `409`. Область ключа: проект + операция; срок хранения необходимо определить в OpenAPI.
- Ошибка содержит `code`, понятное `message`, `request_id` и при необходимости `details`. Фиксируем семантику `400/401/403/404/409/422/429/5xx`; чужая сущность не раскрывается.
- Лимиты запросов задаются по ключу и проекту; `429` содержит `Retry-After`. Числовые квоты ещё не выбраны.
- Успешное принятие сообщения не означает доставку. Ответ и события различают принятие, отправку, доставку, ошибку и неизвестный статус, если канал не даёт подтверждения.
- Ключ нельзя помещать в браузерный JavaScript. Сторонняя форма вызывает backend клиента; публичные виджеты платформы сохраняют собственную модель доступа.
- Для редактирования контента, графов, метаданных и порядка требуется `expected_revision` соответствующего ресурса. При конфликте — `409 revision_conflict` с актуальной ревизией; API не перезаписывает параллельную правку молча. Для метаданных и составных ресурсов такую проверку необходимо добавить там, где её ещё нет.
- `PUT` заменяет весь документ соответствующего ресурса (например, блоки страницы или граф), `PATCH` меняет только переданные поля. Ответ содержит текущую ревизию, опубликованную ревизию при наличии и фактический эффект публикации.
- Сайты и воронки в текущей модели автоматически публикуются при сохранении/восстановлении. API сохраняет эту каноническую семантику: отдельный режим черновика для них не обещан. Разрешение на запись такого контента включает изменение live и требует соответствующего доменного права публикации.

## 1. Контекст проекта — 1 операция

| Метод и путь | Назначение | Предлагаемое разрешение |
| --- | --- | --- |
| `GET /project` | Текущий проект, язык, часовой пояс при наличии, разрешения и доступные возможности ключа | `project:read` |

Новая публичная проекция контекста. Не возвращает полный объект настроек проекта или credentials. Возможности вычисляются сервером, включая ограничения плана.

## 2. CRM — 11 операций

| Метод и путь | Назначение | Предлагаемое разрешение |
| --- | --- | --- |
| `GET /contacts` | Список и поиск контактов; фильтры email, телефон, тег, канал; курсор | `contacts:read` |
| `GET /contacts/{contact_id}` | Карточка контакта: поля, теги, доступные для интеграции переменные | `contacts:read` |
| `POST /contacts` | Создать контакт | `contacts:write` |
| `PATCH /contacts/{contact_id}` | Изменить только переданные поля | `contacts:write` |
| `PATCH /contacts/{contact_id}/variables` | Изменить переменные через `set` и `remove`, сохранив остальные | `contacts:write` |
| `GET /contact-fields` | Схема доступных стандартных и пользовательских полей | `contacts:read` |
| `POST /contact-fields` | Создать пользовательское поле проекта | `contacts:write` |
| `GET /tags` | Каталог CRM-тегов | `contacts:read` |
| `POST /tags` | Создать тег | `contacts:write` |
| `PUT /contacts/{contact_id}/tags/{tag_key}` | Идемпотентно назначить тег | `contacts:write` |
| `DELETE /contacts/{contact_id}/tags/{tag_key}` | Снять конкретный тег | `contacts:write` |

Основа: `apps/server/src/routes/contacts/route.ts` (directory, tags), `contacts/contact-fields.ts` (create, details, fields), `apps/server/src/routes/chats/route.ts` (card, variables).

Доработки: отдельная компактная карточка, точные фильтры email/телефона, контракт частичного обновления и единая публичная схема. Текущий поиск directory не является доказательством точного поиска по идентификатору. Конфликт идентичности возвращает `409`; автоматическое объединение контактов не включать. `external_id`, upsert и политика нескольких интеграций требуют отдельного решения и пока не обещаны.

## 3. Воронки, графы и участие контакта — 12 операций

| Метод и путь | Назначение | Предлагаемое разрешение |
| --- | --- | --- |
| `GET /funnels` | Список доступных воронок | `funnels:read` |
| `GET /funnels/{funnel_id}` | Метаданные и допустимые CRM-этапы, без внутреннего графа | `funnels:read` |
| `POST /funnels` | Создать воронку поддерживаемого вида | `funnels:write` |
| `PATCH /funnels/{funnel_id}` | Изменить доступные для автора метаданные воронки | `funnels:write` |
| `GET /funnels/{funnel_id}/graph` | Прочитать редактируемый граф: узлы, связи и настройки | `funnels:read` |
| `PUT /funnels/{funnel_id}/graph` | Заменить граф с проверкой схемы и ревизии | `funnels:write` |
| `GET /funnels/{funnel_id}/revisions` | Список доступных контрольных точек графа | `funnels:read` |
| `POST /funnels/{funnel_id}/restore` | Восстановить граф из контрольной точки новой ревизией | `funnels:write` |
| `GET /contacts/{contact_id}/funnel-memberships` | Участие контакта в воронках и текущие CRM-этапы | `funnels:read` |
| `POST /contacts/{contact_id}/funnel-memberships` | Подключить к существующей воронке | `funnels:enroll` |
| `PATCH /contacts/{contact_id}/funnel-memberships/{membership_id}` | Сменить допустимый CRM-этап | `funnels:manage` |
| `DELETE /contacts/{contact_id}/funnel-memberships/{membership_id}` | Прекратить участие в выбранной воронке | `funnels:manage` |

Основа: `apps/server/src/routes/funnels/route.ts`, `apps/server/src/routes/chats/route.ts:3215`, `apps/server/src/routes/contacts/route.ts:2480`.

Подключение вызывает ту же `subscribeContactToFunnel`, что и платформа, и может немедленно отправить стартовые сообщения. Повторное подключение уже участвующего контакта не запускает новую рассылку. Неподходящий/отсутствующий канал — доменная ошибка, не обещание доставить сообщение по произвольному email или номеру. Смена CRM-этапа имеет разную семантику для видов воронок; API должен описать побочные эффекты и не обещать одинаковое перемещение курсора. Снятие участия должно соблюдать каноническую отмену отложенных действий; это проверяется до выпуска.

Редактирование графа использует канонические `FunnelSchema` и `saveFunnelDocument`: сохраняются допустимые типы узлов, связи, ссылки на сущности проекта, серверные поля и тарифные ограничения. Доступность вида воронки определяется сервером; закрытый для клиентов `course-funnel` не становится доступным через новый API. Метод метаданных — новая узкая проекция, реализуемая через тот же доменный документ с контролем ревизии. Сохранение/восстановление графа может сразу менять live; оно не является повторным подключением участников. Поведение уже выполняющихся воронок при изменении/удалении узлов необходимо описать и проверить до выпуска. Основа дополнительных операций: `apps/server/src/routes/funnels/route.ts:613`, `:1716`, `:1924`, `:1963`.

## 4. Курсы, модули, уроки и доступы — 23 операции

| Метод и путь | Назначение | Предлагаемое разрешение |
| --- | --- | --- |
| `GET /courses` | Список курсов | `courses:read` |
| `GET /courses/{course_id}` | Метаданные, доступные варианты доступа и потоки | `courses:read` |
| `POST /courses` | Создать курс | `courses:write` |
| `PATCH /courses/{course_id}` | Изменить название, описание, обложку и поддерживаемые настройки курса | `courses:write` |
| `GET /courses/{course_id}/modules` | Список модулей и их порядок | `courses:read` |
| `POST /courses/{course_id}/modules` | Создать модуль | `courses:write` |
| `PATCH /courses/{course_id}/modules/{module_id}` | Изменить метаданные модуля | `courses:write` |
| `PUT /courses/{course_id}/module-order` | Задать порядок модулей | `courses:write` |
| `GET /courses/{course_id}/lessons` | Список уроков, принадлежность модулям и порядок | `courses:read` |
| `GET /courses/{course_id}/lessons/{lesson_id}` | Метаданные одного урока | `courses:read` |
| `POST /courses/{course_id}/lessons` | Создать урок, при необходимости в выбранном модуле | `courses:write` |
| `PATCH /courses/{course_id}/lessons/{lesson_id}` | Изменить метаданные, модуль и поддерживаемые настройки урока | `courses:write` |
| `GET /courses/{course_id}/lessons/{lesson_id}/content` | Прочитать блоки и сведения о ревизиях содержимого урока | `courses:read` |
| `PUT /courses/{course_id}/lessons/{lesson_id}/content` | Заменить блоки урока с проверкой ревизии | `courses:write` |
| `POST /courses/{course_id}/lessons/{lesson_id}/publish` | Опубликовать конкретную проверенную ревизию содержимого урока | `courses:publish` |
| `PUT /courses/{course_id}/lesson-order` | Задать порядок уроков с сохранением связи с модулями | `courses:write` |
| `GET /courses/{course_id}/lessons/{lesson_id}/revisions` | Доступные контрольные точки содержимого урока | `courses:read` |
| `POST /courses/{course_id}/lessons/{lesson_id}/restore` | Восстановить содержимое урока новой ревизией | `courses:write` |
| `GET /courses/{course_id}/shared-sections` | Прочитать общие секции, используемые уроками курса | `courses:read` |
| `PUT /courses/{course_id}/shared-sections` | Обновить общие секции с проверкой ревизии | `courses:write` |
| `GET /contacts/{contact_id}/course-accesses` | Действующие доступы контакта с источником и сроками | `course-access:read` |
| `POST /contacts/{contact_id}/course-accesses` | Выдать ручной доступ к существующему варианту курса/потока | `course-access:grant` |
| `DELETE /contacts/{contact_id}/course-accesses/{grant_id}` | Отозвать конкретный ручной доступ | `course-access:revoke` |

Основа: `apps/server/src/routes/collections/route.ts` (course), `apps/server/src/routes/chats/route.ts:2675` и `:2883`.

Новая публичная проекция должна различать отдельный grant и эффективный доступ от нескольких источников. Удаление ручного grant не отменяет оплаченный доступ и не оформляет возврат. Существующий внутренний маршрут удаления эффективного доступа нельзя переносить наружу без ограничения по источнику. Выдача может отправить приветствие; результат сообщает, был ли доступ выдан и было ли доставлено приветствие. Возвращаемая ссылка не переносит личность ученика и не заменяет его авторизацию.

Авторский контент — канонические блоки платформы, а не отдельный HTML-формат. Модуль и урок должны принадлежать курсу из URL; порядок и перемещения валидируются сервером. Изменение курса/урока не выдаёт доступ ученикам. Изменение вариантов доступа и потоков, массовое изменение прав учеников и генерация курса через AI не добавляются этим расширением. Общие секции нужны для полноценного чтения и изменения существующих уроков со связанными блоками; изменение секции может затронуть несколько уроков.

Основа авторских методов: `apps/server/src/routes/collections/route.ts:1984`, `:2322`, `:3306`, `:3635`, `:3714`, `:3867`, `:3880`, `:3917`, `:3987`, `:4013`. Публичная публикация обязана принимать ожидаемую ревизию; нельзя переносить внутренний вариант «опубликовать что сейчас сохранено» без защиты от гонки. Схемы должны отдельно описать видимость метаданных, контента и общих секций для ученика: наличие `publishedRev` само по себе не доказывает, что публичный player читает только опубликованный снимок. До реализации это нужно проверить на каноническом player; новый скрытый черновой слой здесь не обещан.

## 5. Каталог предложений — 4 операции

| Метод и путь | Назначение | Предлагаемое разрешение |
| --- | --- | --- |
| `GET /products` | Товары, варианты, цены и доступность | `catalog:read` |
| `GET /products/{product_id}` | Один товар с вариантами | `catalog:read` |
| `GET /pricing-plans` | Клиентские тарифные предложения проекта | `catalog:read` |
| `GET /pricing-plans/{plan_id}` | Одно тарифное предложение | `catalog:read` |

Основа: `apps/server/src/routes/catalog-products/route.ts`, `apps/server/src/routes/pricing-plans/route.ts`. Это предложения бизнеса клиента, а не тариф его подписки на OctoFunnel. Нужны стабильные ссылки на сущности и ограниченная публичная проекция вместо всего ответа редактора.

## 6. Продажи — 4 операции

| Метод и путь | Назначение | Предлагаемое разрешение |
| --- | --- | --- |
| `GET /orders` | Заказы продаж бизнеса; фильтры contact, status; курсор | `sales:read` |
| `GET /orders/{order_ref}` | Состав, сумма, валюта и статус конкретного заказа | `sales:read` |
| `GET /subscriptions` | Подписки покупателей бизнеса клиента | `sales:read` |
| `GET /subscriptions/{subscription_ref}` | Состояние одной подписки | `sales:read` |

Основа: `apps/server/src/routes/purchases/route.ts:163`, `apps/server/src/routes/subscriptions/route.ts:65`.

Нужны отдельные карточки и явное ограничение типов заказов: текущая Purchases-проекция также может включать пополнения кошельков. Публичный sales API не должен случайно экспортировать операции оплаты самой платформы. Подписки покупателей не смешиваются с подпиской владельца проекта на платформу. В v1 нет произвольного изменения paid, регистрации внешнего платежа, возврата, списания и отмены подписки.

## 7. Сообщения — 3 операции

| Метод и путь | Назначение | Предлагаемое разрешение |
| --- | --- | --- |
| `GET /channels` | Подключённые каналы и доступные операции без секретов | `channels:read` |
| `GET /contacts/{contact_id}/messages` | История сообщений с курсором | `messages:read` |
| `POST /contacts/{contact_id}/messages` | Отправить текст существующему контакту через разрешённый подключённый канал | `messages:send` |

Основа: `apps/server/src/routes/chats/route.ts:1312` и `:1536`; безопасный каталог каналов надо выделить отдельно.

Предлагаемая первая версия отправки — текст. Прикрепление файлов и медиа отложено. API сохраняет ограничения возможностей каждого канала, включая необходимость доступной идентичности адресата и правила провайдера. Ввод номера/username не даёт права начать переписку. Не обещать статус delivered для каналов без подтверждения доставки.

## 8. Подписки интеграций на события — 7 операций

| Метод и путь | Назначение | Предлагаемое разрешение |
| --- | --- | --- |
| `GET /webhook-endpoints` | Список адресов доставки и подписок на типы событий | `webhooks:read` |
| `GET /webhook-endpoints/{endpoint_id}` | Настройки одной подписки без секрета подписи | `webhooks:read` |
| `POST /webhook-endpoints` | Создать адрес доставки и выбрать события | `webhooks:write` |
| `PATCH /webhook-endpoints/{endpoint_id}` | Сменить URL, события или приостановить доставку | `webhooks:write` |
| `DELETE /webhook-endpoints/{endpoint_id}` | Удалить подписку | `webhooks:write` |
| `GET /webhook-endpoints/{endpoint_id}/deliveries` | Ограниченный журнал попыток и ошибок | `webhooks:read` |
| `POST /webhook-endpoints/{endpoint_id}/deliveries/{delivery_id}/retry` | Повторить доставку сохранённого события | `webhooks:write` |

Это новая подсистема, а не переименование `/api/webhooks`: существующий маршрут принимает сообщения мессенджеров. Требуются транзакционная запись исходящего события, durable worker, подпись, ретраи, журнал, защита исходящих HTTP-запросов от SSRF и ограничения нагрузки.

Предлагаемые типы событий для v1:

| События | Когда публикуются |
| --- | --- |
| `contact.created`, `contact.updated` | Изменились канонические данные контакта |
| `contact.tag_added`, `contact.tag_removed` | Реально изменилось назначение тега |
| `funnel.enrolled`, `funnel.unenrolled`, `funnel.stage_changed` | Изменилось участие или CRM-этап |
| `course.access_granted`, `course.access_revoked` | Создан или отозван отдельный доступ; payload указывает источник |
| `order.paid`, `order.refunded` | Подтверждён платёж/возврат заказа продаж бизнеса |
| `subscription.status_changed` | Изменился статус подписки покупателя |
| `message.received`, `message.sent`, `message.failed` | Принято сообщение, подтверждена отправка или зафиксирована ошибка |

Названия событий и payload пока предложены. Наличие похожего внутреннего события не доказывает полноту покрытия всех источников изменения. Истечение доступа не равно ручному отзыву: событие истечения пока не обещано.

Конверт: `id`, `type`, `version`, `occurred_at`, публичный `project_ref`, `data`. Доставка как минимум один раз; возможны дубли и нарушение порядка. Получатель дедуплицирует по `id`. Подпись включает время и исходные байты тела; секрет выдаётся при создании, ротация доступна владельцу в админке. Повторная доставка использует сохранённое событие с тем же `id`, а не заново собирает текущую сущность. Сроки хранения, backoff и лимит попыток ещё предстоит выбрать.

## 9. Сайты и страницы — 15 операций

| Метод и путь | Назначение | Предлагаемое разрешение |
| --- | --- | --- |
| `GET /sites` | Список сайтов проекта | `sites:read` |
| `GET /sites/{site_id}` | Метаданные и поддерживаемые настройки сайта | `sites:read` |
| `POST /sites` | Создать сайт | `sites:write` |
| `PATCH /sites/{site_id}` | Изменить название, slug, оформление и поддерживаемые настройки | `sites:write` |
| `GET /sites/{site_id}/pages` | Список страниц, вложенность, порядок и признак главной | `sites:read` |
| `GET /sites/{site_id}/pages/{page_id}` | Метаданные страницы | `sites:read` |
| `POST /sites/{site_id}/pages` | Создать страницу, при необходимости вложенную | `sites:write` |
| `PATCH /sites/{site_id}/pages/{page_id}` | Изменить метаданные, SEO-поля и положение страницы в иерархии | `sites:write` |
| `GET /sites/{site_id}/pages/{page_id}/content` | Прочитать блоки страницы и ревизию | `sites:read` |
| `PUT /sites/{site_id}/pages/{page_id}/content` | Заменить блоки страницы с проверкой ревизии | `sites:write` |
| `PUT /sites/{site_id}/page-order` | Задать порядок страниц с сохранением иерархии | `sites:write` |
| `GET /sites/{site_id}/shared-sections` | Прочитать общие секции сайта | `sites:read` |
| `PUT /sites/{site_id}/shared-sections` | Обновить общие секции с проверкой ревизии | `sites:write` |
| `GET /sites/{site_id}/pages/{page_id}/revisions` | Доступные контрольные точки содержимого страницы | `sites:read` |
| `POST /sites/{site_id}/pages/{page_id}/restore` | Восстановить содержимое страницы новой ревизией | `sites:write` |

Основа: `apps/server/src/routes/collections/route.ts` — создание/редактирование коллекций, порядок, содержимое, shared sections и checkpoints. Сайт, страница и родительская страница проверяются в пределах проекта; идентичные локальные номера страниц разных сайтов не должны смешиваться. Slug и вложенность проходят канонические проверки конфликтов и циклов. Для shared sections проверяются ссылки и последствия изменений для всех использующих их страниц.

Сохранение/восстановление публикуется по текущей модели платформы. Отдельный `/publish` для сайтов не добавлен: он создавал бы ложное ожидание, что предыдущий PUT оставил изменения скрытым черновиком. Полноценный draft → preview → publish — отдельное изменение поведения всей платформы и пока не входит в согласованный объём. Восстановление касается контента страницы; оно не обещает откат сайта со всеми метаданными, общими секциями и ссылками одной транзакцией.

Подключение доменов, удаление сайтов/страниц, произвольный executable custom code и пакетный импорт целого сайта не добавляются этим расширением. Редактирование контента может удалять блоки внутри документа PUT; API должен ясно описывать эту семантику замены. Медиа можно выбирать из уже существующих канонических assets; новый upload API остаётся вне состава.

## 10. Схемы авторского контента — 1 операция

| Метод и путь | Назначение | Предлагаемое разрешение |
| --- | --- | --- |
| `GET /authoring-capabilities` | Поддерживаемые для ключа виды воронок, типы узлов/блоков, версии схем и ссылки на их описание | `authoring:read` |

Новая публичная проекция существующих контрактов и доменных registries. Не создавать ручную центральную копию каталога возможностей. JSON-схемы блоков и графов публикуются вместе с OpenAPI и выводятся из канонических схем. Схемы описывают ссылки на медиа/курсы/воронки и поддерживаемые поля; серверные служебные поля не становятся редактируемыми. Ответ не содержит закрытые платформенные промпты, токены или operator-only возможности.

## Что не входит в согласованный v1

| Область | Найденная основа | Почему отдельно |
| --- | --- | --- |
| Запись товаров, тарифов и остатков | catalog-products, pricing-plans | Отдельные правила синхронизации цен, вариантов и остатков |
| Рассылки | broadcasts | Аудитории, расписание, пауза и массовые побочные эффекты |
| Медиа и загрузки | media | Отдельный upload-контракт и жизненный цикл файлов |
| Бронирования | bookings | Полезный кандидат v1.1; нужны контракты слотов, часовых поясов, создания и отмены |
| Задачи и заметки | tasks, notes | Добавлять по подтверждённому спросу интеграций |
| Клубы, партнёрка, affiliate | clubs, partnership, affiliate | Отдельные права, выплаты и жизненные циклы |
| AI-генерация, Video Studio, презентации, OctoReel | Отдельные доменные модули | Асинхронные jobs, тарификация и устойчивое выполнение требуют отдельного API-дизайна |
| Удаление/объединение контактов, массовый импорт | contacts, chats | Разрушительные и долгие операции; нужны preview, jobs и аудит |
| Платёжные команды | purchases, subscriptions, checkout | Отдельный контракт финансовых действий и идемпотентности |
| Настройки провайдеров, команды, домены, биллинг платформы | Внутренние административные маршруты | За границами интеграций с данными бизнеса в первом релизе |
| Универсальный доступ к VFS | MCP/VFS | Сохраняется в MCP; клиентский API публикует предметные ресурсы |

## Основные решения до реализации

1. Достаточно ли сценария «внешняя оплата → ручной доступ», или обязательна полноценная регистрация внешних заказов/платежей? Во втором случае sales scope надо расширить до начала разработки.
2. Нужен ли `external_id` с областью конкретной интеграции и атомарным upsert? Для регулярной двусторонней синхронизации это приоритетный кандидат.
3. Должны ли запись каталога, бронирования или рассылки войти в первый релиз? Сейчас они отложены, а не признаны недоступными в платформе.
4. Числовые квоты, сроки хранения ключей идемпотентности и webhook-журнала, политика ротации и совместимости.

## Готовность к выпуску

- Согласован список операций и сценарии; опубликованы OpenAPI, примеры запросов и ошибок.
- Создание/отзыв/перевыпуск проектных ключей работает в админке; права применяются к каждому методу.
- Все сущности и действия используют канонические доменные операции; изменения видны в админке и MCP там, где домен поддерживает запись.
- Проверены чужие project/entity handles, отозванные ключи, ограниченные права и тарифные запреты.
- Повтор после сетевого сбоя не создаёт второго контакта, доступа, подключения или отправки.
- Публичные read-модели не раскрывают внутренние ids, секреты, чужие данные и биллинг платформы.
- Webhook-события покрывают изменения из API, админки, MCP, платежей и workers; проверены дубли, ретраи и восстановление после сбоя.
- Для сайтов, страниц, графов, курсов, модулей, уроков и общих секций проверены создание/чтение/редактирование через API и последующее открытие в штатном редакторе. Если реализация меняет VFS, доказаны production `createProjectVfs`, буквальный `executeVfsTool(..., 'ls')` и `/api/visualizer/tree`.
- Конфликт ревизий не затирает правку из админки/MCP. Проверены чужие родители и ссылки, сломанные графы, конфликты slug, тарифные лимиты, фактическое появление изменений на публичной странице/в player и восстановление контрольной точки.
- Создание курса и изменение контента не расширяют доступ учеников. Чтение авторского контента и схем не раскрывает закрытые возможности платформы.
- Сквозные сценарии из начала документа проверены на developer-канале после реализации и штатного деплоя.

Исследование основано на исходном коде текущего session worktree. Работающий клиентский API и его поведение на live в рамках этого исследования не реализовывались и не проверялись. Изменений приложения, схем БД и маршрутов нет.
