Документация внутренних API: минимальный жизненный набор
Внутренний сервис, к которому ходят два-три соседних сервиса в той же команде. Документации нет — «и так все знают, что туда писать». Через год команда вырастает, ходоков становится десять, половина — из других команд. Каждая интеграция начинается с переписки в чате, ответов в стиле «посмотри в коде вот тут», и через пару итераций кто-то ломает прод, потому что никто не знал про обязательное поле tenant_id.
Внутренние API часто документируют по принципу «мы сами знаем, потом расскажем». Это работает, пока команда из трёх человек, и проваливается, как только проект перерастает чат. Разберу минимально жизнеспособный набор документации для внутреннего API — что обязательно, что необязательно, и почему «полный гайд как у публичного» здесь не нужен.
Чем внутренний API отличается от публичного
Не тем, что про него можно меньше знать. А тем, что у него другая аудитория и другие тратные точки.
- Аудитория — коллеги. Они могут зайти в код, написать в чат, спросить голосом. Внешний клиент — нет.
- Скорость изменений выше. Публичный API — 1-2 крупных релиза в год. Внутренний — еженедельно, иногда чаще.
- Контракт менее формальный. Поломал — починили вместе. С внешними клиентами так не получится.
- Бюджет на документацию ниже. Никто не выделит технического писателя на каждую внутреннюю кишку.
Из этого следует: документация должна быть минимальной по объёму, максимально близкой к коду, и обновляться сама, насколько возможно.
Минимальный жизненный набор
То, без чего внутренний API превращается в чёрный ящик:
- Список endpoint-ов с методами, путями, кратким описанием.
- Схемы запросов и ответов в машиночитаемом виде (OpenAPI, gRPC proto, GraphQL schema).
- Пример вызова с реальными данными — curl, Postman, скрипт на питоне.
- Описание аутентификации: где взять токен, как его подсовывать.
- Список ошибок с кодами и что они означают.
- Контакты команды-владельца и ссылка на канал саппорта.
Всё. Если есть это — потребитель API сможет интегрироваться без личного коучинга. Если нет — каждое внедрение становится тикетом «помогите подключиться».
Список endpoint-ов
Самый частый вопрос потребителя: «у вас есть метод для X?». Если отвечать на него можно только чтением кода, документации нет.
Минимум — таблица или список с одной строкой на endpoint:
## Endpoints
- `GET /v1/users/{id}` — получить пользователя.
- `POST /v1/users` — создать пользователя.
- `PATCH /v1/users/{id}` — обновить поля пользователя.
- `DELETE /v1/users/{id}` — soft-delete, восстановление через `/users/{id}/restore`.
- `GET /v1/users` — список с пагинацией. Фильтры: `tenant_id`, `email`, `created_after`.
- `POST /v1/users/{id}/restore` — восстановить soft-deleted.
Если их сорок и больше — генерируй из OpenAPI или из аннотаций в коде. Руками поддерживать такой список — путь к расхождению с реальностью.
Схемы — обязательно машиночитаемые
Внутренний API без схемы — это «угадай, что прилетит в этом поле». Схема нужна не ради красивых страниц, а ради:
- Генерации клиентов на стороне потребителей.
- Автоматической проверки контракта в CI.
- Mock-сервера для разработки.
- Базы для рендера документации.
OpenAPI или gRPC proto хранятся в репозитории сервиса, рендерятся через Redoc/Swagger UI на простом сайте, в идеале — публикуются на внутренний хостинг. Не отдельный «документ для людей», а тот же файл, который читают тулзы.
Я обычно делаю так: /openapi.json отдаёт сам сервис на отдельном эндпоинте, /docs рендерит его в HTML. Внутри компании такого «приколочено гвоздём к сервису» оказывается достаточно.
Аутентификация: один абзац, без воды
Внутренние API часто используют что-то простое: service-to-service токен, mTLS, JWT от внутреннего OIDC. Описание занимает абзац:
## Auth
Все запросы требуют заголовок `Authorization: Bearer <token>`.
Токены выдаёт внутренний service-to-service auth (`auth.internal/v1/token`).
Время жизни 1 час, перевыпуск через client_credentials.
В deploy-конфигах — variable `INTERNAL_AUTH_TOKEN`, обновляется sidecar-ом.
Без токена — 401. Истёк — 401, нужен retry с новым токеном.
Не пиши длинные эссе про OAuth flows для внутреннего API. Скажи, где взять, как использовать, что будет, если нет.
Пример запроса с реальными данными
Описание endpoint-а в OpenAPI — это спецификация. Пример — это шпаргалка для копирования. Они нужны оба.
curl -X POST https://users.internal/v1/users \
-H 'Authorization: Bearer eyJhbGc...' \
-H 'Content-Type: application/json' \
-H 'X-Tenant-Id: acme' \
-d '{
"email": "alice@acme.com",
"name": "Alice",
"role": "admin"
}'
Был случай: у нас внутренний сервис требовал заголовок X-Tenant-Id на каждый запрос. В OpenAPI он был описан как обязательный, но в примерах его не было. Все потребители забывали его поставить, и каждое подключение начиналось с «у нас 400, что не так?». Поправили примеры — поток вопросов исчез за неделю.
Ошибки: коды и расшифровка
Не «400 Bad Request — что-то не так». Список конкретных кодов с расшифровкой и что делать клиенту:
## Errors
- `email_taken` (409) — email уже зарегистрирован.
Клиент: предложить вход или восстановление пароля.
- `tenant_not_found` (404) — `X-Tenant-Id` не существует или нет доступа.
Клиент: проверить заголовок и права.
- `quota_exceeded` (429) — превышена квота на создание пользователей.
Клиент: показать сообщение, повторить через час.
- `internal_error` (500) — серверная ошибка. См. `request_id` в теле.
Клиент: ретрай через 5 секунд, эскалация на 3-й попытке.
Это та же таблица, что для публичного API, но короче — внутренние клиенты обычно знают одно-двух разработчиков, и могут спросить уточнение в чате.
Идемпотентность, ретраи, тайм-ауты
Внутренний API работает в среде, где сетевые ошибки — норма. Если потребитель не знает, что метод POST /transfer идемпотентен по заголовку Idempotency-Key, он либо вообще не делает ретрай (теряет запрос), либо делает (создаёт дубль).
## Reliability
- POST `/transfer` принимает заголовок `Idempotency-Key` (UUID).
Повторный запрос с тем же ключом возвращает первый ответ.
Ключ хранится 24 часа.
- GET-эндпоинты можно ретраить свободно, они всегда идемпотентны.
- Тайм-аут на запрос — 30 секунд серверный, 60 секунд рекомендуем клиенту.
- При 5xx — exponential backoff с jitter, минимум 1 секунда между попытками.
Этот раздел экономит часы разбора инцидентов «почему у нас N дублей в базе».
Версионирование внутри компании
Семантика «v1 / v2 / breaking changes» во внутреннем API проще, чем в публичном, но всё равно нужна. Минимум:
- Версия в URL (
/v1/) или в заголовке (API-Version: 1) — выбери одно. - Поддержка одной предыдущей major-версии минимум 3-6 месяцев после релиза новой.
- Анонс breaking changes в общий канал минимум за 2 недели.
- В CHANGELOG — список потребителей, которые потенциально пострадают.
Без этого «обновили API, два сервиса слегли» становится регулярным.
Контакты и канал поддержки
Самое часто забываемое поле. На странице документации обязательно — две строки:
## Owners
- Team: `users-platform`
- Slack: `#users-platform`
- On-call: см. PagerDuty schedule `users-platform-oncall`
Без этого потребитель пишет в общий чат, его пинают по двум-трём командам, и время на инцидент растёт. С таким блоком — он сразу попадает к нужным людям.
Что НЕ нужно класть во внутреннюю документацию
- Tutorials в стиле «getting started». Внутренний потребитель — это не новичок, ему не нужны 10 шагов «установите curl и сделайте свой первый запрос». Хватит примера.
- Ребрендинг и маркетинг. «Сервис Users — мощное решение для управления учётными записями». Никто не читает.
- FAQ из десяти вопросов. Один-два частых вопроса можно вынести в самый верх как «known issues», но не больше.
- Полный API reference как для публичного. OpenAPI и Redoc этого закрывают. Не дублируй вручную.
- Глубокую архитектурную документацию. Это другой жанр (ADR, design docs), её ищут отдельно.
Где документация живёт
Три рабочих варианта, которые видела:
- Markdown в репозитории сервиса. Просто, версионируется с кодом, читается прямо в GitHub/GitLab. Минус — найти трудно, если репозиториев много.
- Внутренний docs-сайт (Backstage TechDocs, MkDocs на корп. поддомене). Удобный поиск, единый стиль. Минус — нужна инфраструктура и регулярное обновление.
- Wiki (Confluence, Notion). Знакомо нетехническим коллегам. Минус — отрыв от кода, быстро устаревает, версии путаются.
Лично для документации API я выбираю первый или второй: Markdown в репо как источник правды, рендер на сайт через CI. Confluence-овые страницы про API устаревают быстрее, чем команда пишет следующую правку.
Что автоматизировать
Внутренний API меняется часто, документация «руками» отстаёт. Автоматизировать как минимум:
- Генерацию OpenAPI из кода (FastAPI, NestJS Swagger plugin, drf-spectacular для DRF, springdoc для Spring).
- Генерацию страницы reference из OpenAPI (Redoc CLI в CI).
- Проверку, что схемы коммитятся вместе с кодом (CI fail, если
openapi.jsonотличается от того, что генерируется). - Генерацию клиентов для основных потребителей (TypeScript, Python, Go) — закидываем в их репозитории через CI.
Если потребители получают типизированный клиент — половина вопросов «что в этом поле» отпадает сама.
Чек-лист минимальной документации внутреннего API
- Список endpoints с описанием, генерируется из кода или коммитится.
- Схема (OpenAPI / proto / GraphQL), доступная как файл и как рендер.
- Пример curl-запроса, копируется и работает.
- Раздел про аутентификацию: один абзац, конкретно.
- Список ошибок с кодами и инструкцией клиенту.
- Раздел про надёжность: идемпотентность, ретраи, тайм-ауты.
- Версионирование: где смотреть, как анонсируются изменения.
- Контакты команды: канал, on-call, owners.
- CHANGELOG, обновляется при релизе.
Если этот лист закрыт — внутренний API готов к тому, чтобы к нему подключались без помощи команды-владельца. Дальше можно усложнять (туториалы, расширенные сценарии, гайды по типичным интеграциям), но это уже доп., а не базовый минимум. Пока базовый минимум не закрыт — никакие красивые странички про «как мы построили платформу» не помогут.