Проектирование API: как выбрать стиль, договориться о контракте и не переделывать через год
Симптом плохо спроектированного API всегда один: каждая новая интеграция превращается в проект. Партнёр не может подключиться без вашего разработчика, мобильное приложение требует переделки серверной части, а на вопрос «что вернётся в этом поле» никто не отвечает без похода в код. Мы в Code Pilots делаем продукты с интеграциями и чаще всего приходим именно в такую ситуацию.
Если совсем коротко. API — это контракт, а не набор ссылок: его проектируют до реализации, потому что менять его после подключения потребителей дорого. Рабочий дефолт для внешних потребителей — REST с описанием в OpenAPI; GraphQL берут, когда фронтенду нужны гибкие запросы по вложенным данным; gRPC — для внутреннего обмена между своими сервисами.
Четыре вещи определяют, переживёт ли API вторую интеграцию: идемпотентность мутирующих запросов, обрабатываемый формат ошибок, продуманное версионирование и актуальная документация. Всё остальное — детали.
В статье
- Зачем проектировать отдельно
- REST, GraphQL, gRPC
- Ресурсы и коды ответов
- Идемпотентность
- Ошибки, которые обрабатываются
- Версионирование
- Пагинация и выборки
- Доступ и лимиты
- Контракт первым
- Тесты и наблюдаемость
- Внутреннее, партнёрское, публичное
- Вебхуки и события
- Сколько стоит
- Ошибки проектирования
- С чего начать
- Часто задаваемые вопросы
Зачем проектировать API отдельно
Кажется, что API появляется сам: написали серверную часть, открыли несколько адресов — готово. Проблемы начинаются на втором потребителе.
Контракт нельзя тихо изменить. Пока API использует только ваше приложение, всё правится одновременно. Как только появился партнёр или мобильный клиент в сторе, у вас на руках старая версия у пользователей, которую нельзя обновить принудительно.
Каждое неудачное решение умножается. Странное имя поля, невнятный код ответа, отсутствие идентификатора запроса в ошибке — всё это придётся объяснять каждому новому потребителю, и объяснять будут ваши разработчики вместо работы.
API определяет скорость продуктовых изменений. Если серверная часть отдаёт данные ровно в том виде, в котором их показывает текущий интерфейс, любая переделка экрана превращается в переделку сервера.
Практический пример из наших проектов: у PetShop мобильное приложение вышло за две недели именно потому, что серверная часть была спроектирована как API-платформа, а не как приложение с прикрученными адресами. Как устроена серверная часть в целом, разобрано в материале про бэкенд.
Три стиля: REST, GraphQL, gRPC
Универсального ответа нет, но есть работающее правило выбора по типу потребителя.
| Стиль | Когда подходит | Плюсы | Чем платите |
|---|---|---|---|
| REST | Публичные и партнёрские API, интеграции, мобильные приложения | Понятен всем, кешируется, отлично документируется в OpenAPI, работает без клиентских библиотек | Много запросов на сложных экранах, лишние поля в ответах |
| GraphQL | Фронтенд с гибкими запросами по вложенным данным, несколько разных клиентов | Клиент берёт ровно нужные поля, одна точка входа | Сложнее кеширование, лимиты сложности запросов, выше порог входа для партнёров |
| gRPC | Внутренний обмен между своими сервисами, высокая частота вызовов | Быстрый бинарный протокол, строгий контракт, кодогенерация | Не работает напрямую из браузера, тяжелее отлаживать, нужен контроль над обоими концами |
Практика 2026 года: REST остаётся безопасным дефолтом для всего, что смотрит наружу, gRPC живёт внутри между сервисами, а GraphQL чаще всего появляется в роли агрегатора над ними — шаблон backend-for-frontend, который стал доминирующей корпоративной моделью.
Отдельно про мобильные приложения: им обычно нужны крупные экономные ответы под конкретный экран, а не десять запросов на отрисовку списка. Это решается либо агрегирующими методами в REST, либо GraphQL-слоем; как устроена клиентская часть, которая всё это потребляет, — в материале про архитектуру мобильного приложения.
Ресурсы, методы и коды ответов
Скучная часть, на которой экономят время, а потом платят поддержкой.
Ресурсы — существительные, действия — методы. `/orders`, `/orders/{id}`, `/orders/{id}/items`, а не `/getOrderList` и `/createNewOrder2`. Это не эстетика: предсказуемая схема позволяет партнёру угадывать адреса, не открывая документацию.
Коды ответов по назначению. 200 — успех, 201 — создано, 400 — ошибка в запросе, 401 — не аутентифицирован, 403 — нет прав, 404 — нет объекта, 409 — конфликт состояния, 422 — не прошла валидация, 429 — превышен лимит, 5xx — проблема на вашей стороне. Ответ «200 OK» с телом `{"error": "not found"}` ломает любую типовую обработку на клиенте.
Единый стиль имён. Один регистр, одинаковые названия для одних и тех же сущностей во всех методах, единый формат даты — ISO 8601 с часовым поясом. Смешанные `user_id` и `userId` в одном API стоят каждому интегратору по часу на выяснение.
Явные единицы измерения. `amount` в копейках или в рублях, `weight` в граммах или килограммах — пишется в документации и, желательно, в имени поля.
Идемпотентность: почему это обязательно
Самое дорогое, что можно пропустить. Сеть ненадёжна: клиент отправил запрос на создание заказа, ответ не дошёл из-за таймаута, клиент повторил. Без защиты у вас два заказа и два платежа.
Рабочее решение — ключ идемпотентности: клиент генерирует уникальный идентификатор операции (обычно UUID) и передаёт его в заголовке. Сервер запоминает результат по этому ключу и на повтор отдаёт тот же ответ, а не создаёт новую сущность. Именно так работает Stripe, и это фактический стандарт для платежных и заказных API.
Что должно быть идемпотентным: всё, что создаёт или меняет состояние и стоит денег или порождает документ. Заказы, платежи, возвраты, отправка сообщений, начисление бонусов.
Практическое следствие для проекта: если сейчас идемпотентности нет, добавить её позже сложнее, чем кажется — нужно хранить результаты операций и договариваться с потребителями о новом заголовке. Дешевле заложить сразу.
Ошибки, которые можно обработать
Ошибка — это тоже часть контракта, и её формат проектируют так же, как успешный ответ. Минимальный набор в теле ответа:
- Машинный тип ошибки. Строковый код вроде `insufficient_stock`, по которому клиент строит логику. Не текст сообщения — тексты меняются.
- Человеческое сообщение. Что случилось, на языке пользователя, если ошибку планируют показывать.
- Детали по полям. Для валидации: какое поле не прошло и почему.
- Идентификатор запроса. Одна строка, по которой ваша поддержка находит запрос в логах. Экономит часы на каждом обращении.
Отдельное правило: не отдавайте внутренние подробности в ошибках наружу. Трассировка стека и тексты запросов к базе — это и утечка информации, и бесполезный для клиента шум. Внутрь — полный лог с идентификатором, наружу — короткий понятный ответ.
Версионирование и обратная совместимость
Версия нужна не потому, что так принято, а потому что вы не можете обновить чужие клиенты по своему желанию.
| Изменение | Ломает совместимость | Что делать |
|---|---|---|
| Добавили новое поле в ответ | Нет | Выпускать в текущей версии |
| Добавили необязательный параметр запроса | Нет | Выпускать в текущей версии |
| Добавили новый метод | Нет | Выпускать в текущей версии |
| Переименовали или удалили поле | Да | Новая версия или поле-синоним на переходный период |
| Поменяли тип значения | Да | Новая версия; молча менять нельзя |
| Поменяли смысл значения при том же типе | Да, и это худший случай | Новое поле с новым именем |
| Сделали параметр обязательным | Да | Новая версия |
| Поменяли код ответа в существующем сценарии | Да | Новая версия |
Механизмы. Версия в пути (`/v1/orders`) — операционно самый простой: видно в логах, в документации и в разговоре с партнёром. Заголовок с версией — гибче, так делает GitHub. Версии по дате с обратной трансформацией ответов — подход Stripe, самый дружелюбный к потребителю и самый дорогой в поддержке. Механизм важнее написания: главное, чтобы старые клиенты продолжали работать.
Срок жизни версии. Объявляйте заранее, сколько поддерживаете предыдущую и как уведомляете о выключении. Партнёру нужно время на доработку, а вам — понятная точка, после которой можно удалить старый код.
Пагинация и большие выборки
Место, где API начинает падать на реальных объёмах.
Пагинация обязательна с первого дня. Метод, который отдаёт «все заказы», однажды получит клиента с миллионом записей и уронит и себя, и базу.
Смещение против курсора. Пагинация по смещению проста, но на больших объёмах медленная и даёт дубли при вставках между запросами. Курсорная — сложнее в реализации, стабильна и быстра; для растущих данных это правильный выбор.
Лимиты. Максимальный размер страницы задаётся на сервере, а не доверяется клиенту. Иначе первый же запрос с лимитом в сто тысяч записей станет инцидентом.
Тяжёлые выгрузки — отдельным механизмом. Полная синхронизация каталога или истории заказов делается не через обычный метод списка, а через асинхронную задачу: клиент запрашивает выгрузку, получает идентификатор, потом забирает готовый файл. Так тяжёлая операция не занимает соединение и не мешает остальным.
Аутентификация, права и лимиты
Три разные задачи, которые часто путают.
Аутентификация. Кто вызывает: API-ключ для сервер-сервер интеграций, токены OAuth для доступа от имени пользователя, подпись запроса для чувствительных операций. Ключи должны быть отзываемыми и с разными правами для тестового и рабочего контура.
Авторизация. Что именно этому потребителю разрешено. Партнёр видит свои заказы, а не все; интеграция склада читает остатки, но не меняет цены. Права проверяются на сервере по каждому объекту, а не только по методу.
Лимиты. Ограничение частоты запросов защищает вас от чужой ошибки в цикле. Лимит должен возвращать код 429 и заголовок с временем, через которое можно повторить, — тогда нормальный клиент подстроится сам.
Плюс базовая гигиена: только защищённое соединение, никаких ключей в адресе запроса (они попадают в логи), разные ключи на каждого потребителя, чтобы можно было отключить одного, не сломав остальных.
Контракт первым: OpenAPI и документация
Подход, который экономит больше всего времени на проектах с несколькими командами: сначала пишется и согласуется контракт, потом код.
Контракт в формате OpenAPI — это машиночитаемое описание методов, параметров, схем данных и ошибок. Версия 3.1 полностью выровнена с JSON Schema, а обновление 3.2 добавило структурированную навигацию по тегам и стриминговые типы данных.
Что даёт контракт-первый подход на практике: фронтенд и мобильная команда начинают работу до готовности серверной части, работая с заглушками по контракту; документация и клиентские библиотеки генерируются из того же файла, а не пишутся отдельно и не устаревают; изменения контракта видны в код-ревью как обычный диф; несоответствие реализации контракту ловится автоматическими проверками.
И организационное следствие: API — это продукт, у которого пользователи разработчики. Документация для него не приложение, а интерфейс. Если партнёр не может подключиться, читая её без звонка вашему инженеру, значит продукт не готов.
Тестирование и наблюдаемость
Три уровня, каждый закрывает свой класс проблем.
Контрактные тесты. Проверяют, что реализация соответствует OpenAPI: те же поля, типы, коды ответов. Ловят самый частый класс поломок — «поле переименовали, забыли предупредить».
Интеграционные тесты сценариев. Полный путь: создать заказ, оплатить, отменить, получить возврат. Важнее, чем проверка отдельных методов, потому что ломается обычно стык.
Наблюдаемость в бою. Метрики по методам: частота, время ответа по перцентилям, доля ошибок 4xx и 5xx. Логи с идентификатором запроса. Алерт на рост доли ошибок у конкретного потребителя — часто это единственный способ узнать, что партнёр обновился и что-то сломал.
У себя мы держим мониторинг обменов со статусами и алертами: для интеграционного контура это первый признак проблемы, который приходит раньше, чем письмо от партнёра.
Внутреннее, партнёрское и публичное API
Требования отличаются радикально, и это стоит решить до проектирования.
| Тип | Кто потребитель | Что обязательно | Что можно упростить |
|---|---|---|---|
| Внутреннее | Свои сервисы и приложения | Стабильность контракта внутри релиза, метрики | Публичная документация, SDK, длинная поддержка версий |
| Партнёрское | Ограниченный круг известных интеграторов | Документация, версии со сроком жизни, тестовый контур, разные ключи | Самообслуживание, публичная песочница |
| Публичное | Любой разработчик | Полная документация, самостоятельная регистрация, песочница, лимиты, SDK, канал поддержки | — |
Частая ошибка — объявить внутреннее API партнёрским, не добавив ни документации, ни тестового контура. Каждый новый партнёр тогда подключается через переписку с вашим разработчиком, и это тихо съедает недели инженерного времени в год. В корпоративном контуре, где потребителей и систем много, счёт идёт уже на человеко-месяцы: про этот класс систем есть отдельный материал про enterprise-системы.
Отдельная тема — обмен большими объёмами и события. Если партнёрам нужны уведомления об изменениях, вебхуки дешевле, чем опрос вашего API по расписанию; а для сложного обмена между многими системами обычно правильнее не API-к-API, а управляемый слой обмена, как в материале про интеграционную шину.
Вебхуки и события
Обратная сторона API: иногда данные должны идти от вас к потребителю, а не наоборот. Если партнёр раз в минуту опрашивает ваш метод «есть новые заказы?», вы платите за это нагрузкой, а он — задержкой.
Что отправлять. Событие с типом, идентификатором объекта и временем. Полное состояние объекта в теле — удобно, но означает, что вы отдаёте данные наружу без запроса; иногда правильнее отдать только идентификатор, а детали потребитель забирает сам.
Подпись и проверка. Вебхук должен быть подписан секретом, чтобы получатель убедился, что запрос от вас. Без подписи любой может прислать «оплата прошла».
Повторы и порядок. Получатель будет недоступен — это норма. Нужны повторы с растущими интервалами, ограничение числа попыток и журнал доставки. Порядок событий не гарантируется, поэтому у каждого события должны быть номер и время, а обработчик на стороне партнёра должен быть готов к дублям.
Ответ получателя. Договоритесь, что считается успешной доставкой: код 200 и ничего больше. Ждать от получателя бизнес-результата в ответе на вебхук — плохая идея: он начнёт делать в обработчике тяжёлую работу и упираться в таймаут.
Чем это лучше опроса. Меньше пустых запросов, меньше задержка, ниже нагрузка на обе стороны. Но появляется новая обязанность: следить за доставкой и давать партнёру страницу с историей отправок, иначе разбор «мы не получили событие» превращается в переписку.
Сколько стоит и от чего зависит
Стоимость определяют четыре фактора: число сущностей и методов, требования к типу API (внутреннее заметно дешевле публичного), объём миграции существующих потребителей и необходимость поддерживать старые версии.
Ориентиры по нашим работам: отдельная интеграция или проектирование и разработка API под конкретный обмен — от 150 тыс. до 1,5 млн ₽, продукт с API-платформой под процесс — от 1,7 млн ₽, аудит существующего API и архитектуры — от 200 до 600 тыс. ₽. Точная сумма считается по числу методов и потребителей: оценка проекта бесплатная — опишите задачу.
Что почти всегда недооценивают в смете: документацию и тестовый контур (без них API формально есть, а подключиться нельзя), миграцию существующих потребителей на новую версию и поддержку старой версии в течение переходного периода. Прикинуть порядок суммы с учётом этих работ можно в бесплатном калькуляторе оценки.
Ошибки проектирования
Проектировать API под текущий экран. Через год интерфейс меняется, и сервер приходится переписывать вместе с ним.
Отдавать 200 на ошибку. Клиент не может отличить успех от неудачи стандартными средствами, и каждый интегратор пишет свою обработку.
Пропустить идемпотентность. Двойные заказы и платежи всплывают на первом же сетевом сбое, а добавлять защиту потом дороже.
Не заложить пагинацию. Метод «отдать всё» работает до первого крупного клиента.
Оставить документацию на потом. Она не появляется никогда, и её роль начинает исполнять ваш разработчик в переписке.
Ломать совместимость молча. Переименованное поле роняет чужие интеграции в продакшене, и это самый быстрый способ испортить отношения с партнёром.
Если API уже живёт и мешает — начинать стоит не с переписывания, а с диагностики: аудит кода и архитектуры обычно показывает, что достаточно привести к порядку формат ошибок, добавить версионирование и описать контракт, а не строить всё заново.
С чего начать
Три шага до проектирования. Первое — выписать потребителей: свои приложения, партнёры, интеграции с учётными системами, будущее мобильное приложение. Именно список потребителей определяет тип API и требования к нему. Второе — выписать сущности и операции над ними в терминах бизнеса: заказ, товар, остаток, клиент, платёж. Третье — договориться о четырёх сквозных решениях: формат ошибок, идемпотентность, пагинация, версионирование.
С этими тремя списками контракт пишется за несколько дней, а не рождается по ходу разработки — и переделывать его через год не придётся.
Часто задаваемые вопросы
По типу потребителя. REST — безопасный дефолт для всего, что смотрит наружу: партнёры, интеграции, мобильные приложения. GraphQL оправдан, когда у вас несколько клиентов и фронтенду нужны гибкие запросы по вложенным данным. gRPC — для внутреннего обмена между своими сервисами с высокой частотой вызовов. Частый гибрид: gRPC внутри, REST наружу, GraphQL как агрегатор для фронтенда.
Чтобы повтор запроса при сетевом сбое не создавал второй заказ или второй платёж. Реализация: клиент передаёт уникальный ключ операции в заголовке, сервер сохраняет результат по этому ключу и на повторный запрос возвращает тот же ответ вместо создания новой сущности. Обязательно для всего, что создаёт документы или списывает деньги.
Так, чтобы старые клиенты продолжали работать. Версия в пути вида `/v1/` операционно самая простая: видно в логах и в разговоре с партнёром. Есть варианты с заголовком версии и с версиями по дате и обратной трансформацией ответов — они дружелюбнее к потребителю, но дороже в поддержке. Главное — заранее объявить срок жизни версии и правило уведомления об отключении.
Подход, при котором контракт API в формате OpenAPI пишется и согласуется до реализации. Он даёт три вещи: фронтенд и мобильная команда начинают работу по заглушкам, не дожидаясь сервера; документация и клиентские библиотеки генерируются из того же файла и не устаревают; расхождение реализации с контрактом ловится автоматическими проверками.
Зависит от числа сущностей и методов, типа API (внутреннее дешевле публичного), объёма миграции существующих потребителей и необходимости поддерживать старые версии. У нас проектирование и разработка API под конкретный обмен — от 150 тыс. до 1,5 млн ₽, продукт с API-платформой — от 1,7 млн ₽, аудит существующего API — от 200 до 600 тыс. ₽. В смету обязательно входят документация и тестовый контур: без них подключиться к API нельзя.
Разработка с Code Pilots
Code Pilots — студия заказной разработки полного цикла: веб- и мобильные продукты с нетривиальной бизнес-логикой, интеграциями и высокими нагрузками. Интеграционная экспертиза — наша сильная сторона: обмены с 1С и хранилищами данных, очереди, отказоустойчивая доставка, мониторинг обменов со статусами и алертами.
Из практики: у PetShop мобильное приложение вышло за две недели, потому что серверная часть была спроектирована как API-платформа; у B2B-дистрибьютора обмен с хранилищем держит отклик меньше полусекунды на RabbitMQ и Go; в проекте «Радиоэлементы» поставщики подключаются к обмену прайсами сами, без участия разработчиков.
Если API уже есть — начнём с аудита контракта и скажем, что можно привести в порядок доработками, а что придётся переделывать. Если API только проектируется — соберём контракт до первой строки кода. Обсудить проект →