Обратная совместимость API: как не сломать клиентов
Изменение одного поля в JSON может остановить мобильное приложение, интеграцию партнёра или платёжный сценарий. Разбираем, какие изменения безопасны, когда нужен новый endpoint и как проверять контракт до релиза.

Удаление поля из ответа, смена типа идентификатора или новый обязательный параметр способны сломать клиентов без единой ошибки в вашем серверном коде. Этот материал — для HTTP API, описанных в OpenAPI 3.1: здесь разобраны практические способы менять контракт без остановки старых приложений, интеграций и фоновых задач.
Что именно считается поломкой
Клиент ломается не только тогда, когда получает HTTP 500. Ошибка возникает и в более тихих случаях: декодер не принимает новый тип, мобильное приложение не умеет обработать неизвестный статус, а бизнес-логика считает отсутствие поля тем же самым, что и его значение.
Сначала зафиксируйте контракт не в виде «примерно такого JSON», а как набор наблюдаемых свойств:
- адрес ресурса и HTTP-метод;
- обязательные параметры запроса и допустимые значения;
- типы полей, формат дат, единицы измерения и правила округления;
- структура успешного ответа и ошибок;
- коды HTTP и порядок, в котором клиент может получить ответы;
- ограничения по размеру, пагинации, сортировке и частоте запросов.
Клиентский код — это не только ваш фронтенд. В реестре потребителей должны быть мобильные версии, SDK, партнёрские интеграции, скрипты аналитики, выгрузки и неизвестные вам пользователи публичного API.
Сначала найдите потребителей
Перед изменением соберите доступные данные: логи маршрутов, заголовок User-Agent, версии SDK, владельцев интеграций и дату последнего вызова. Логи не покажут клиента, который обращается к API редко или через общий прокси, поэтому список потребителей нельзя считать полным только по метрикам.
Обратная совместимость — способность новой версии сервера продолжать корректно обслуживать старых клиентов, которые используют прежний контракт. Контракт здесь означает наблюдаемое соглашение между клиентом и сервером: какие данные отправляются, какие ответы возвращаются и как трактуются ошибки.
Четыре способа менять API
1. Расширять старый контракт
Самый дешёвый вариант — добавлять необязательные поля в ответ и новые необязательные параметры в запрос. Старый клиент их проигнорирует, а новый получит дополнительную информацию. Так можно добавить, например, отображаемое имя, ссылку на изображение или новый фильтр.
Цена — необходимость заранее проверить, умеют ли все клиенты игнорировать неизвестные поля. Строгий десериализатор или схема, настроенная на точное совпадение, может отвергнуть расширенный ответ. Поэтому правило «добавление поля безопасно» верно не всегда.
2. Добавлять новый endpoint
Если меняется форма ресурса, смысл поля или набор обязательных условий, отдельный маршрут обычно безопаснее правки старого. Старый клиент продолжает обращаться к прежнему адресу, а новые версии переходят на новый.
Вы платите за дублирование документации, мониторинга и иногда серверной логики. Важно заранее определить срок поддержки старого маршрута: «новый endpoint уже есть» не означает, что старый можно немедленно удалить.
3. Версионировать контракт
Версия в URL, заголовке или другом явно задокументированном механизме нужна, когда несовместимость неизбежна: поменялась структура ответа, удаляется ресурс или прежное значение стало означать другое. Для внешнего API версия в адресе вроде /v2/ проще для диагностики и маршрутизации, но увеличивает число вариантов, которые нужно поддерживать.
Версия в заголовке оставляет URL чище, зато ошибку сложнее воспроизвести вручную, а прокси и кеши должны корректно учитывать этот заголовок. Выбирайте один механизм и используйте его последовательно, а не смешивайте варианты для разных методов без причины.
4. Перевести клиентов поэтапно
Сначала сервер начинает отдавать новый контракт небольшой доле клиентов или только внутренним потребителям. Затем вы обновляете клиентов, сравниваете ошибки и задержки, объявляете дату прекращения старого варианта и лишь после этого удаляете его.
Этот подход требует флага, метрики версии и владельца миграции. Его цена — более долгий период поддержки. Зато он снижает риск массового отказа, особенно для мобильных приложений, которые обновляются не сразу.
| Подход | Когда подходит | Главная цена | Риск для старых клиентов |
|---|---|---|---|
| Расширение ответа | Новые необязательные данные | Проверка строгих декодеров | Низкий, но не нулевой |
| Новый endpoint | Меняется форма или смысл ресурса | Дублирование поддержки | Низкий |
| Новая версия API | Несовместимые изменения | Параллельные контракты | Низкий после миграции |
| Постепенная миграция | Много клиентов и длинный цикл обновления | Флаги, сроки и дополнительные метрики | Самый управляемый |
Изменения, которые часто выглядят безопасными
Типы, null и обязательность
Замена строки на число несовместима, даже если числовое значение кажется логичнее. То же относится к переходу от целого числа к дробному, от локальной даты к дате со временем и от одного формата идентификатора к другому.
Добавление обязательного поля в запрос ломает старые клиенты сразу после выкладки. Если поле действительно необходимо, временно принимайте отсутствие и возвращайте понятную ошибку только после завершения миграции. В ответе различайте «поле отсутствует», null и пустую строку: разные SDK могут трактовать их по-разному.
Перечни значений и статусы
Добавить новый статус на сервере легко, но старый клиент может иметь ветку «всё неизвестное — ошибка». Поэтому расширяемые перечисления нужно описывать как расширяемые, а клиентам следует предусматривать безопасный сценарий для неизвестного значения.
Нельзя без предупреждения менять смысл существующего статуса. Если раньше paid означал подтверждённую оплату, превращать его в «платёж отправлен на проверку» нельзя: формально значение осталось прежним, а бизнес-логика уже другая.
Ошибки и пагинация
Клиент может зависеть от конкретного кода HTTP, но не должен разбирать текст сообщения как стабильный интерфейс. Сохраняйте структуру ошибки и добавляйте машинно-читаемый код, если его ещё нет. Текст оставляйте для человека и логов.
Смена offset-пагинации на cursor-пагинацию — не косметика. Она меняет параметры, порядок обхода и поведение при добавлении записей. Старый режим лучше сохранить отдельным параметром или маршрутом, пока все потребители не перейдут на новый.
Самое опасное изменение — не то, после которого сервер отвечает ошибкой, а то, после которого старый клиент получает валидный, но иначе интерпретируемый ответ.
Как проверять совместимость до релиза
Тесты только сервера не доказывают, что контракт сохранён. Нужна проверка с позиции потребителя: старый запрос должен получать старый смысл ответа, а новый сервер не должен требовать от него неизвестных обязательных данных.
- Зафиксируйте базовую схему. Сохраните опубликованную спецификацию OpenAPI 3.1 и примеры реальных запросов. Проверка: у изменения есть ссылка на конкретный endpoint и список затронутых клиентов.
- Составьте матрицу версий. Отметьте, какая версия клиента вызывает какой маршрут и какие поля использует. Проверка: для каждой комбинации есть владелец или честная пометка «потребитель неизвестен».
- Запустите контрактные проверки. Отправьте старые запросы на новую сборку и сравните не только схемы, но и коды HTTP, обязательные поля, значения статусов и ошибки. Проверка: несовместимое изменение попадает в отчёт до выкладки.
- Проверьте неизвестные значения. Добавьте в тестовый ответ новый статус и дополнительное поле. Проверка: клиент не падает и выбирает безопасное поведение, например показывает общий статус или сохраняет запись для повторной обработки.
- Включите наблюдение после релиза. Разделяйте ошибки по версии клиента, endpoint и типу ответа. Проверка: вы видите рост ошибок конкретного потребителя, а не только общий процент 4xx и 5xx.
Для публичного API полезно проверять совместимость двух направлений: новый сервер со старым клиентом и новый клиент со старым сервером. Второй сценарий особенно важен при поэтапном обновлении нескольких сервисов, когда версии выкатываются не одновременно.
Когда подход ломается
- Удаление поля под видом уборки. Оно может использоваться клиентом, которого нет в репозитории.
- Смена единиц измерения. Метры и сантиметры имеют одинаковый числовой тип, но совершенно разный результат.
- Переиспользование имени. Поле status нельзя наполнять новым набором смыслов без миграции.
- Незаметная смена времени. Переход с локального времени на UTC меняет отображение и расчёты, даже если формат строки остался прежним.
- Разный порядок полей как причина падения. В JSON порядок обычно не должен иметь значения, но самописные парсеры и подписи запросов могут зависеть от сериализации. Такой клиент нужно отдельно обнаружить и вывести из зоны риска.
Если обратная совместимость требует десятков условных веток на сервере, это сигнал остановить расширение старого контракта. Сохраните стабильный слой адаптации, вынесите новую модель в отдельную версию и установите дату удаления старого поведения.
Как выбирать стратегию
Добавляйте поле или необязательный параметр, если смысл старых данных не меняется и клиенты способны игнорировать расширение. Выбирайте новый endpoint, если ресурс остаётся тем же, но его форма заметно меняется. Версия API нужна для несовместимого поведения, которое нельзя выразить временной адаптацией.
При большом числе внешних клиентов не полагайтесь на объявление в документации. Нужны период поддержки, контакт владельца интеграции, метрика вызовов старой версии и понятный ответ на вопрос: что получит клиент после даты отключения.
Что делать дальше
Начните с инвентаризации одного критичного endpoint: кто его вызывает, какие поля реально читает и какие ошибки возникают на проде. Затем добавьте автоматическую проверку старого контракта в процесс сборки и дашборд с разбивкой по версиям клиентов. После этого можно планировать миграцию, но не удаляйте старый маршрут, пока его вызовы не упали до согласованного порога и владельцы интеграций не подтвердили переход.
Логотип и фирменный стиль: Три концепции разного характера, палитра с обоснованием, проверка на читаемость в малом размере и в один цвет.
Спросить за 60 ₽Источники: OpenAPI Specification 3.1.0, RFC 9110: HTTP Semantics
Частые вопросы
Можно ли добавлять новые поля в JSON без новой версии API?
Что делать, если нужно удалить старое поле?
Нужно ли версионировать API при каждом изменении?
Что будет, если клиент не успеет перейти на новую версию?
Достаточно ли тестов на сервере?
Что делать, если старый клиент падает на новом статусе?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.