Главная / Статьи

Распространенные ошибки в контракте API, нарушающие надежность фронтенда

Десять распространённых ошибок в контракте API, разрушающих надёжность фронтенда: от изменения формата ответов и возврата кода 200 OK при каждой ошибке до нестабильной пагинации, вместе с способами их устранения.

Издатель: REACTAPP.TOP18 мин чтения4119 слов

Источники: 10 API Design Mistakes That Make Frontend Developers Hate You

API может казаться идеальным на бумаге, но при этом сделать жизнь всех, кто разрабатывает на его основе, крайне сложной.

Эндпоинт возвращает корректный JSON. Код состояния соответствует ожидаемым значениям. Ответ приходит быстро. С точки зрения бэкенда задача кажется завершённой. Однако инженеры фронтенда видят совсем другую картину. Каждый запрос требует дополнительной проверки на случай ошибок. Эндпоинты, похожие по форме, возвращают совершенно разные данные. Сообщения об ошибках похожи на строки из журнала логов, а не на то, что должен видеть пользователь. Функция пагинации ведёт себя непоследовательно для разных ресурсов. Не существует надёжного способа отличить пустое значение от отсутствующего или отсутствующее значение от запроса, завершившегося с ошибкой.

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

Четкие контракты API сокращают количество догадок. Они указывают, что прошло успешно, а что — неудачно, какие поля являются опциональными, как сортируются результаты, существуют ли дополнительные страницы, а также какие значения можно безопасно кэшировать или сравнивать со временем. Слабые контракты переносят всю эту неоднозначность наружу, заставляя каждый веб-сайт, мобильное приложение, панель управления и интеграцию с третьими сторонами создавать свою собственную интерпретацию.

Именно в этот момент появляется недовольство. Фронтенд перестает рассматривать API как источник истины и начинает воспринимать его как что-то, что нужно обходить.

1. Возврат различной структуры ответа от каждого эндпоинта

Один эндпоинт возвращает простой массив. Другой помещает свой пейлоад внутрь ключа data. Третий использует поля result, message и success. Маршрут с пагинацией хранит свои записи под ключом items, в то время как соседний маршрут называет этот же элемент rows. Если удалить объект через один контроллер, вы получите удаляемую запись; если через другой — ничего вообще.

Если рассматривать каждый случай по отдельности, ни один из этих ответов не является сложным для понимания. Проблемы возникают, когда команда фронтенда пытается создать общую, повторно используемую логику для взаимодействия с API в целом.

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

const users = response.data.result.users;
const orders = response.items;
const reports = response.data.rows;

Очевидной проблемой является дублирование кода маппинга. Более серьезной проблемой — непредсказуемость. Когда разработчик фронтенда сталкивается с эндпоинтом, с которым он раньше не работал, у него нет способа определить его структуру по поведению остальной части API.

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

Ответ в формате списка всегда может содержать записи вместе с определенными метаданными. Команда всегда может вернуть либо обновленный ресурс, либо четко определенный объект с результатом. Ошибки всегда следуют по одной задокументированной структуре. Пустое состояние может быть представлено одинаковым образом в похожих конечных точках.

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

2. Использование кода 200 OK для любого результата

Некоторые API отвечают кодом 200 OK независимо от того, что на самом деле произошло, скрывая реальный результат внутри тела ответа:

{
  "success": false,
  "message": "User not found"
}

Этот паттерн обычно возникает как упрощение для работы с клиентской частью: каждый запрос обрабатывается одинаково, и фронтенд просто проверяет флаг success в теле запроса.

На практике это лишает нас преимуществ, которые уже предоставляет транспортный слой бесплатно.

HTTP-клиенты, инструменты мониторинга, кэши, прокси, API браузеров и библиотеки для получения данных все построены на категориях кодов состояния. Отклоненный запрос автоматически переходит в состояние ошибки. Успешное создание можно отличить от простого чтения. Конфликты можно обрабатывать иначе, чем недопустимые данные или отсутствие токена аутентификации.

Когда каждый ответ возвращается с кодом 200, фронтенд должен при каждом запросе проверять его тело. Если забыть об этом, неудачная операция может показаться успешной. Общая логика повторных попыток становится рискованной, поскольку клиент больше не может отличить временную ошибку сервера от ошибки валидации. Снижается также возможность наблюдения за системой: панели управления отображают волну успешных запросов, в то время как на самом деле пользователи сталкиваются с ошибками.

Противоположной ошибкой является объединение всех сбоев в код 500. Отсутствие записи, некорректный запрос, проблемы с правами, дублирующиеся значения и сбои на последующих этапах обработки — это не один и тот же тип сбоя. Объединение всех этих случаев в одну общую ошибку сервера заставляет фронтенд анализировать текст сообщения, чтобы понять, что именно пошло не так.

Коды состояния никогда не предназначались для передачи полного бизнес-смысла ответа, но они должны точно классифицировать результат. Затем тело ответа может содержать любые структурированные детали, которые действительно необходимы интерфейсу.

Фронтенду не следует разбирать фразу «Электронная почта уже существует», чтобы понять, что речь идет о конфликте.

3. Отправка текста об ошибке без какой-либо структуры

Простое сообщение об ошибке может быть достаточным для тех, кто читает логи, но оно редко предоставляет интерфейсу достаточно информации для работы.

Представьте, что конечная точка регистрации возвращает что-то вроде:

{
  "message": "Validation failed"
}

Из этого у клиента нет способа узнать, какое поле вызвало проблему, какое правило было нарушено, сработали ли одновременно несколько полей или даже связана ли эта ошибка вообще с формой.

Сталкиваясь с этой проблемой, разработчики вынуждены писать хрупкую логику сопоставления строк: если в тексте упоминается «электронная почта», ошибка привязывается к полю ввода почты; если упоминается «пароль», отображается предупреждение, связанное с паролем. Как только серверная часть меняет формулировки, исправляет опечатку, изменяет регистр или добавляет локализацию, эта логика молча перестает работать.

Лучший подход заключается в разделении того, что должна понимать машина, и того, что должен читать человек. Ответ может включать стабильный код ошибки, детали по каждому полю и контекстуальную информацию о запросе, наряду с сообщением, которое безопасно можно отобразить или занести в логи.

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

Всё это не означает необходимости включения в ответ сырых следов исключений или строк ошибок базы данных — они обычно раскрывают внутренности реализации и всё равно создают путаницу у пользователя. Задача бэкенда — преобразовывать низкоуровневые сбои в стабильные, чётко определённые ошибки домена.

Хорошо спроектированный пакет ошибок позволяет клиенту ответить на три основных вопроса: к какой категории относится сбой, к какой части интерфейса он принадлежит и есть ли у пользователя возможность что-либо с этим сделать.

Если пренебречь такой структурой, каждый фронтенд-клиент будет вынужден самостоятельно интерпретировать текст, возвращаемый бэкендом.

4. Рассмотрение null, отсутствующих полей, пустых строк и пустых массивов как взаимозаменяемых

Любой факультативный элемент в API должен иметь чёткое определение того, что означает его отсутствие.

Отсутствие поля в данных запроса может означать, что оно просто не запрашивалось. Значение null может указывать на то, что система явно знает о его отсутствии. Пустая строка может означать, что пользователь решил не вводить текст, или же это может быть следствием того, что старый код некорректно обрабатывает отсутствующие значения. Пустой массив может означать, что действительно нет ни одного соответствующего элемента, или же это может означать, что загрузка связи завершилась с ошибкой, и вместо неё тихо был использован пустой список.

Когда API смешивает эти форматы представления без единообразия, у фронтенда нет надежного способа определить, с какой именно ситуацией он имеет дело.

Поле профиля может полностью отсутствовать в одном ответе и появиться как null в другом. Конечная точка списка может возвращать null вместо пустого массива, когда показать что-либо невозможно, что означает, что каждый компонент должен выполнять защитные проверки перед обработкой данных. Дата, которая недоступна, может отображаться как пустая строка, что нарушает работу библиотек парсинга, ожидающих корректного формата. Связь, загрузка которой не удалась, может незаметно отображаться как пустой массив, из-за чего настоящая ошибка выглядит точно так же, как законный пустой состояние.

Естественным результатом является код фронтенда, заполненный защитными мерами нормализации, например:

const records = Array.isArray(response.records)
  ? response.records
  : [];

const displayName =
  response.displayName ?? response.name ?? "";

Определенная степень нормализации является разумной — внешние системы по своей природе неопределенны, и клиентам следует защищаться от этого. Проблема возникает тогда, когда фронтенд больше не справляется с реальной внешней неопределенностью, а вместо этого пытается исправить ситуацию с помощью API-контракта, который не может определить, что означает понятие «ничего».

Решение заключается в том, чтобы API намеренно определил, что представляет собой каждый вид отсутствия данных. Коллекции должны оставаться коллекциями даже тогда, когда в них ничего нет, а не превращаться в null. Поля, которые допускают отсутствие данных, должны быть явно разрешены к null-значению, а не становиться таковыми случайно. Кроме того, при полном отсутствии поля должна существовать задокументированная причина — например, использование разреженных наборов полей, ограничения по правам доступа или специфичные для конкретного эндпоинта проекции данных.

Как только отсутствие данных приобретает четкое значение, состояния интерфейса становятся более понятными. Фронтенд может различать ситуации «комментариев нет», «комментарии не включены в этот ответ» и «загрузка комментариев завершилась с ошибкой».

Это три совершенно разных пользовательских опыта, хотя на первый взгляд все они могут казаться идентичными: пустой раздел комментариев, в котором ничего не отображается.

5. Прямая передача сырых моделей базы данных через API

Прямая сериализация объекта ORM и его отправка в качестве ответа могут показаться наименее сложным решением — объект уже находится в памяти, так зачем создавать дополнительный слой только для его преобразования?

Проблема в том, что строка базы данных и контракт API существуют по разным причинам.

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

Как только фронтенд начинает использовать такие поля, как user_role_id, deleted_at, или сложную структуру вложенных связей, возникающую случайно из-за определённой настройки ORM, бэкенд оказывается в ловушке. Переименование столбцов, разделение таблицы, изменение способа загрузки данных или переход на другой сервис — всё это, что изначально должно было быть чисто внутренними изменениями, вдруг превращается в нарушающие работу публичный API изменения.

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

Специальная модель ответа решает эту проблему, выступая в качестве барьера. Она чётко определяет, на каких полях могут полагаться клиенты, преобразует представления на уровне хранения в понятные концепции домена и предотвращает случайное просачивание внутренней структуры в публичный контракт.

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

Разработчики фронтенда должны иметь возможность создавать приложения на основе стабильных концепций продукта, а не на основе текущей структуры таблицы базы данных.

6. Создание пагинации, на которую нельзя полагаться

Пагинация кажется простой до тех пор, пока основные данные не изменяются во время прокрутки.

Эндпоинт принимает параметры page и limit, применяет смещение и возвращает фрагмент записей. Фронтэнд отображает элементы управления страницами и перебирает список. Всё это работает гладко, пока не добавляются новые строки, не удаляются старые или порядок сортировки не фиксируется.

Представьте, что пользователь просматривает первую страницу. В это время в начало коллекции добавляется новая запись. Когда пользователь запрашивает вторую страницу, элемент, который он уже видел на первой странице, снова появляется, так как смещение изменилось. В другом месте другая запись может вообще быть пропущена и никогда не отобразиться.

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

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

Пагинация с использованием офсета в сочетании с гарантированным порядком сортировки хорошо работает для большинства наборов данных. Однако для часто обновляющихся источников информации или очень больших коллекций пагинация с использованием курсора обычно проявляет лучшую стабильность при постоянных изменениях. Выбор подхода зависит от продукта, но в любом случае в спецификации необходимо четко определить, как клиент будет перемещаться по коллекции.

Ответ на запрос о пагинации также должен содержать ответы клиента на некоторые практические вопросы: существует ли следующая страница? Какое значение курсора следует использовать в следующем запросе? Является ли общее количество элементов точным, оценкой или просто намеренно не указанным? Что произойдет, если страница, которую в настоящее время просматривает клиент, станет пустой из-за удаления элементов?

Когда конечная точка возвращает записи без надежного способа их навигации, фронтенд вынужден отображать элементы пагинации, которым он не может полностью доверять. Пользователи видят дубликаты и отсутствующие записи, а за проблему, на самом деле связанную с условиями взаимодействия, винят интерфейс.

7. Отправка дат без указания часового пояса

Дата почти никогда не является просто обычным строковым значением.

Временная метка без указания часового пояса может означать время локального сервера, время хранения в базе данных, собственное локальное время конечного пользователя или значение, предназначавшееся для формата UTC, но потерявшее свой сдвиг при форматировании. В таком случае фронтенд вынужден отображать, сравнивать, сортировать и запускать действия на основе значения, значение которого определено лишь частично.

Возьмём ответ вида "2026-08-04 09:00:00". Он не указывает, к какому именно моменту времени он относится. Сессия браузера в Пакистане и сессия в Канаде могут интерпретировать эту строку по-разному, а смена временных зон только усугубляет неопределённость.

Как общее правило, абсолютные временные метки должны содержать явный сдвиг или быть представлены в стандартном формате UTC. Календарные значения, не связанные с конкретным моментом времени, такие как дата рождения или дата выставления счёта, должны оставаться простыми датами и не должны тайно меняться при переходе между часовыми поясами.

Это различие имеет реальные последствия. Если преобразовать дату рождения, сохранённую в виде полуночи по UTC, в местное время, некоторые пользователи увидят неверный день — день ранее. Наоборот, если считать запланированное событие просто строкой с датой, все увидят, что оно происходит в соответствии с их местным временем, а не в один и тот же общий момент.

API также должен соблюдать единообразие в точности и формате данных, используемых на всех своих конечных точках. Проблема возникает, когда одна конечная точка возвращает значения в секундах по системе Unix, другая — в миллисекундах, а третья — в каком-то специфическом формате строки.

Разработчики фронтенда способны форматировать дату в соответствии с настройками региона пользователя. Однако они не могут воссоздать часовой пояс, о котором API изначально им не сообщал.

Проблемы, связанные с часовыми поясами, особенно раздражают, потому что обычно всё работает нормально для команды бэкенда, находящейся в своем собственном часовом поясе, а сбои возникают только у пользователей в других регионах.

8. Принудительное объединение данных на простых экранах с помощью цепочек запросов

Страница сначала загружает текущего пользователя. Как только пользователь получен, она загружает его организацию. После получения информации об организации загружаются разрешения. Только после этого загружаются записи, которые пользователю действительно разрешено просматривать.

Несмотря на то, что каждый отдельный эндпоинт отвечает быстро, в целом экрану может потребоваться несколько секунд, прежде чем он покажет что-либо полезное.

Такая модель обычно возникает, когда API разрабатывается исключительно с учётом нормализованных ресурсов серверной части, без уделения должного внимания тому, как клиент на самом деле читает эти данные на практике. Дизайн серверной части выглядит аккуратным и упорядоченным, в то время как клиенту приходится организовывать целый последовательный процесс для отображения одной страницы.

Разработчики фронтенда решают эту проблему, вкладывая состояния загрузки одно внутрь другого. Сбой на каком-либо этапе может привести к тому, что экран останется частично инициализированным. Обработка отмены становится сложной, когда пользователь уходит в другое место в процессе получения данных. К тому же одна и та же информация часто запрашивается несколько раз, поскольку несколько компонентов каждый по отдельности имеют свою ссылку в этой цепочке.

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

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

Запросы в графовом стиле, слои бэкенд-фор-фронтенд, параметры расширения ресурсов и специально разработанные эндпоинты — всё это действенные инструменты для этой цели. Конкретный архитектурный стиль имеет меньшее значение, чем результат: общие экраны не должны заставлять браузер поочередно исследовать граф связей бэкенда с помощью отдельных запросов.

API не получает более высоких оценок за дизайн только потому, что каждый эндпоинт однозначно соответствует отдельной таблице в базе данных. Хорошо спроектированный контракт также должен учитывать то, как на другом конце фактически используются данные.

9. Скрытие бизнес-правил за «зеленым светом»

Иногда эндпоинт для выполнения операций сообщает о успехе, хотя финальный результат ещё не сформирован полностью.

Запрос на генерацию отчёта помещается в очередь для обработки на фоне. Платеж переводится в статус «в ожидании». Операция удаления запланирована на выполнение после истечения определённого периода хранения. Поставщик третьей стороны подтверждает получение запроса, но не то, что завершил работу.

Когда API отвечает тем же пакетом данных об успехе, который используется для действительно завершённых операций, фронтенд рискует сообщать пользователю информацию, которая пока не является правдой.

Интерфейс может сообщать «Отчёт готов», хотя его ещё формируют. Он может удалять строку из списка, даже если сервер отметил её лишь для последующего удаления. Он может показывать оплату как завершённую, в то время как поставщик может отклонить её позже.

Клиентам необходим способ различать операции, которые уже выполнены, приняты, всё ещё находятся в ожидании, имеют частичный успех или требуют дальнейших действий. Это не мелкая деталь реализации — она напрямую определяет, что интерфейс может честно сообщить пользователю.

Для операций, требующих мгновенного выполнения, часто целесообразно моделировать их как отдельный ресурс задачи или операции с уникальным идентификатором и полем статуса. Состояния, находящиеся в промежуточном состоянии, следует отображать явно, а не сводить к общему статусу «успех». Когда результат частичный, в ответе должно быть четко указано, что было выполнено успешно, а что — нет.

Та же дисциплина полезна при повторных попытках. Если запрос истек временем, но сервер уже может им заниматься, фронтенд не должен просто отправлять запрос снова в надежде на успех. Наличие идентификатора операции или ключа идемпотентности позволяет клиенту безопасно повторять попытки или проверять статус, не рискуя возникновением дублирующих побочных эффектов.

Ответ о успехе должен отражать только те гарантии, которые сервер может предоставить в данный момент — ничего больше.

Это действительно раздражает, когда инженерам фронтенда приходится сами разбираться, означает ли статус 200, что задача выполнена, только начата или просто «ничего не пошло не так».

10. Развитие спецификации без плана для старых клиентов

Изменение API кажется легким, когда одновременно с ним разрабатывается единая ветка фронтенда. Вы меняете название поля, корректируете код клиента, выпускаете оба вместе и переходите дальше.

Но в реальных условиях всё редко происходит таким образом.

Кто-то может оставить открытую вкладку старого браузера после развертывания. Мобильное приложение может оставаться установленным на устройстве месяцами без обновлений. Интеграция с сторонним сервисом может учесть ваши изменения только при своем следующем обновлении. Устаревший кэшированный пакет JS может продолжать обращаться к новому бэкенду, ожидая при этом старую структуру ответа.

Удаление или переименование поля может незаметно нарушить работу кода, и у членов команды бэкенда не будет возможности сразу же это исправить.

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

Управление этим начинается с понимания того, кто на самом деле использует API. Внутренние клиенты, развертываемые синхронно с бэкендом, обычно могут терпеть более быструю итерацию, чем публичные интеграции — но даже чисто внутренние системы выигрывают от постепенных изменений и поэтапного устаревания старых полей.

Практические тактики включают: введение новых полей до устаревания старых, отслеживание реальных данных использования для определения момента, когда можно безопасно что-то убрать, проведение тестов контрактов с ключевыми потребителями, а также применение надлежащей версионности API, когда семантика слишком сильно меняется, чтобы её можно было плавно обновить на месте.

Ничто из этого не означает, что каждый исторический контракт должен поддерживаться вечно. Суть заключается в том, чтобы не позволять сбоям со стороны клиента превращаться в «проблему кого-то другого с развертыванием» только потому, что бэкенд был выпущен раньше.

Как только другие системы начинают использовать API, его поведение имеет жизненный цикл, который значительно превышает срок существования кода, который его изначально реализовал.

Дружелюбие к фронтенду не означает позволять фронтенду диктовать условия API

Хорошо спроектированный API — это не тот, который просто воспроизводит формат, наиболее удобный для использования конкретным компонентом React. Это лишь приведёт к появлению другой, не менее хрупкой формы связи.

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

Команды фронтенда вполне способны справляться с настоящей сложностью — пагинация, состояния ошибок, анимации загрузки, асинхронные процессы — всё это они создают регулярно. На самом деле людей истощает случайная сложность, возникающая из-за неоднородной структуры ответов и отсутствия семантических сигналов, а не из-за самой проблемы.

Наиболее здоровые отношения между бэкендом и фронтендом формируются путем совместной разработки таких контрактов. Фронтенд определяет, какие различия действительно влияют на поведение интерфейса для пользователя. Бэкенд уточняет, какие гарантии он может дать с уверенностью и какие операции естественным образом выполняются в течение более длительного времени. Результатом становится API-контракт, который не принадлежит полностью ни одной из сторон.

API вызывает недовольство с того момента, когда начинает распространять свою собственную неопределённость наружу.

Каждый неоднозначный код состояния превращается в условную ветку на стороне клиента. Каждая несогласованная структура ответа превращается в отдельную функцию-адаптер. Каждый отсутствующий структурированный код ошибки приводит к использованию нестабильных методов сравнения строк. Каждое нестабильное поле требует дополнительной защиты в виде проверки на null.

Всё это не означает, что хорошо спроектированный API устраняет всю работу на стороне фронтенда.

Он лишь позволяет разработчикам фронтенда тратить время на реальное создание продукта, а не постоянно пытаться расшифровать то, что имел в виду бэкенд.