$ sudo teach|
    $ sudo teach|
    IT school
  • Telegram
  • Партнёрам
  • Все курсы
$ sudo teach IT
OOO "SALEPROFIT"Контакты и реквизитыIT-Park Logo

Школа

  • Блог
  • Проверить сертификат

Сотрудничество

  • Стать учителем
  • Партнёрская программа
  • О проекте

Право

  • Оферта
  • Политика конфиденциальности

© 2023–2026 $ sudo teach IT™. All Rights Reserved. Public user contributions licensed under CC BY-SA 4.0 license with attribution required
TelegramGitHubYouTube
ГлавнаяБлогКак читать и писать REST API контракты: статусы, ошибки и схемы ответов

Как читать и писать REST API контракты: статусы, ошибки и схемы ответов

$ sudo teach IT
·20 июля 2026 г.·9 мин·91
Как читать и писать REST API контракты: статусы, ошибки и схемы ответов

Разберём типовые ошибки в дизайне API и научимся фиксировать контракт: коды HTTP, формат ошибок, версионирование и примеры запросов/ответов.

Содержание
Что такое “контракт” REST API и почему он важнее “эндпоинтов”HTTP статусы: как читать и назначать коды правильноКак читать контракт по статусамБазовая матрица статусов (и самые частые ошибки)Практический шаблон: какие статусы должны быть для каждого кейсаФормат ошибок: единообразие вместо “разных сообщений”Что должен содержать error responseПример: согласованный формат ошибокОшибка как “контракт”: статусы + типы ошибок + правилаСхемы ответов: как проектировать структуру данных так, чтобы её не “ломали” релизыМинимальный набор правил для схемПример: JSON schema на практике (без формализма, но строгостью)Версионирование API: как не превратить “v2” в костыльТипы версионированияКак фиксировать совместимость: “breaking change” и “non-breaking change”Практическая стратегия эволюции контрактаКак писать контракт: структура документа и “что обязательно описать”Что включить в контракт для каждого endpointЧеклист контрактного дизайнераПримеры запросов/ответов: как показать контракт “в действии”Пример 1: создание ресурса с валидациейПример 2: конфликт при уникальностиПример 3: идемпотентный POST (если у вас платежи/создания с ретраями)

REST API контракты — это договор между клиентом и сервером. Он редко выглядит как документ в PDF, чаще — как набор правил: какие статусы отдаём, как форматируем ошибки, как версионируем API, какие поля и в каком виде возвращаем. Хороший контракт снижает стоимость разработки: клиенту не нужно гадать, серверу — “угадывать”, что имел в виду фронтенд. Плохой контракт приводит к бесконечным “а давайте добавим ещё одно поле”, хаотичным обработчикам и росту количества несовместимых релизов.

Ниже разберём, как читать REST API контракт (т.е. оценивать качество и предсказуемость по тому, как он описан) и как писать контракт самим: коды HTTP, единый формат ошибок, схемы ответов, версионирование и примеры запросов/ответов. Поймём, где чаще всего ломается взаимодействие и как это фиксировать документально.


Что такое “контракт” REST API и почему он важнее “эндпоинтов”

В REST часто говорят про “эндпоинты”: GET /users, POST /orders. Но на практике контрактом становится:

  1. Семантика методов (что означает GET, что — POST, как трактуем идемпотентность).
  2. Коды HTTP (какие сценарии какой код должны получать).
  3. Тела ответов (JSON schema, набор полей, допустимые типы, nullability, форматы дат).
  4. Ошибки (структура, единообразие, читаемость, привязка к полям/причинам).
  5. Поведение при ошибках (повторяемость, retry-after, side effects).
  6. Версионирование и эволюция (как меняем ответы без “поломки” клиентов).
  7. Корреляция (id запроса, trace id в заголовках) — особенно важно при инцидентах.

Контракт — это то, что клиенту нужно знать, чтобы корректно обработать успешный сценарий и “плохой”.


HTTP статусы: как читать и назначать коды правильно

Как читать контракт по статусам

Хороший контракт:

  • использует статусы с предсказуемой семантикой;
  • не смешивает бизнес-ошибки с HTTP “по случайности”;
  • документирует, какие статусы могут прийти для каждого метода;
  • не возвращает 200 OK вместо ошибок там, где ошибка очевидна (хотя иногда это встречается исторически — и это уже технический долг).

Плохой контракт:

  • возвращает один и тот же код на всё (“вместо 404 всегда 200 и флаг success”);
  • использует 500 вместо 400/409;
  • возвращает 201 без Location;
  • отдаёт 204 при наличии тела ответа;
  • не различает “неавторизован” и “запрещено”.

Базовая матрица статусов (и самые частые ошибки)

Ниже — практическая матрица, которую удобно держать в голове при дизайне:

Успех

  • 200 OK — стандартный ответ “успешно, есть содержимое”.
  • 201 Created — создан ресурс. Часто вместе с Location.
  • 202 Accepted — принято в обработку асинхронно, результат будет позже.
  • 204 No Content — успешно, но тело отсутствует (например, DELETE).
  • 206 Partial Content — редкий случай для диапазонов.

Типичная ошибка: возвращать 201, когда на самом деле “обновили”, а не создали (лучше 200 или 204, либо 409/201 — зависит от бизнес-логики).

Клиентские ошибки

  • 400 Bad Request — неверный формат/валидация на уровне запроса (JSON невалиден, поля не соответствуют схеме).
  • 401 Unauthorized — запрос без аутентификации или с невалидным токеном.
  • 403 Forbidden — аутентификация есть, но нет прав (например, роли не подходят).
  • 404 Not Found — ресурс не существует (или скрываем существование политикой).
  • 405 Method Not Allowed — метод не поддерживается на данном ресурсе.
  • 409 Conflict — конфликт состояния: например, попытка создать дубликат, optimistic locking, пересечение уникальности.
  • 410 Gone — ресурс удалён и больше не доступен.
  • 422 Unprocessable Entity — иногда используется для “семантически неверных данных” при корректном синтаксисе (это удобно, но не стандартно в духе REST-учителей; если используете — сделайте это последовательно).

Типичные ошибки:

  • использовать 401 вместо 403 (аутентификация/авторизация перепутаны);
  • кидать 500 при проблемах в запросе клиента;
  • смешивать “ошибка валидации” и “бизнес-ограничение” в одном 400 без деталей.

Серверные ошибки

  • 500 Internal Server Error — неожиданные ошибки.
  • 502/503/504 — проблемы с upstream/временная недоступность/таймауты.

Типичная ошибка: отдавать 500 при “ограничение бизнеса нарушено” — вместо 409/422.

Практический шаблон: какие статусы должны быть для каждого кейса

Хорошая практика — явно фиксировать для каждого endpoint:

  • минимум “успехов” (1–2 кода);
  • ошибки: “валидация”, “аутентификация”, “авторизация”, “не найден”, “конфликт”, “неизвестная ошибка”.

Пример: POST /v1/payments

  • 201 Created
  • 400 — ошибка формата/валидации
  • 401 — нет/плохой токен
  • 403 — нет прав
  • 409 — конфликт (например, payment already exists for idempotency key)
  • 500 — непредвиденное

Формат ошибок: единообразие вместо “разных сообщений”

Что должен содержать error response

Единый формат ошибок решает две задачи:

  1. Клиенту проще обработать ошибки программно.
  2. Становится возможно наблюдать и диагностировать (и в проде, и в тестах).

Обычно ошибки описывают так:

  • HTTP статус (как уже обсудили);
  • структура JSON с полями:
    • type — категория/тип ошибки (машиночитаемый идентификатор);
    • title — краткое читаемое описание;
    • status — дублирование статуса (чтобы не терять при проксировании);
    • detail — расширенное объяснение (не слишком длинное);
    • instance — идентификатор конкретной ошибки/запроса (опционально);
    • errors — массив полевых ошибок для валидации.

Семантика type/title/detail часто берётся из RFC 7807 (“Problem Details for HTTP APIs”). Это хорошая основа даже если вы не обязаны следовать RFC на 100%.

Пример: согласованный формат ошибок

code
{
  "type": "https://api.example.com/problems/validation-error",
  "title": "Validation error",
  "status": 400,
  "detail": "Request body contains invalid fields.",
  "instance": "6b1c9f8a-1f6c-4b6c-9b52-2f7d4c4b9f3f",
  "errors": [
    {
      "field": "email",
      "code": "INVALID_FORMAT",
      "message": "Email has an invalid format."
    },
    {
      "field": "amount",
      "code": "MIN_VALUE",
      "message": "Amount must be greater than 0."
    }
  ]
}

Ключевые детали:

  • errors — для валидации, когда можно привязать проблему к полям.
  • code — машинный код, чтобы фронтенд мог делать локализацию и обработку без парсинга текста.
  • message — человекочитаемое сообщение (обычно в одном языке или в языке сервиса; локализацию лучше делать на клиенте по code).

Ошибка как “контракт”: статусы + типы ошибок + правила

Контракт нужно фиксировать так, чтобы любой читающий API знал:

  • для 400 вы отдаёте validation-error и обязательно заполняете errors;
  • для 409 — conflict с полями code и, при необходимости, resource/conflictField;
  • для 401/403 — какие заголовки и поля присутствуют.

Например, для 401 полезно отдавать WWW-Authenticate, а для “повторяемости” иногда добавляют Retry-After (чаще для 429/503).


Схемы ответов: как проектировать структуру данных так, чтобы её не “ломали” релизы

Минимальный набор правил для схем

В контракте нужно определить:

  1. Формат дат — лучше ISO 8601 (например, 2026-07-20T12:34:56Z).
  2. Идентификаторы — типы (string/uuid/int64), формат.
  3. Nullable-поля — что означает null, а что поле отсутствует.
  4. Enum’ы — ограниченный набор значений с документированными вариантами.
  5. Стабильность полей — что нельзя удалить без версионирования.
  6. Пустые коллекции — возвращать [], а не null.

Типичная ошибка: “поле есть только иногда”. Клиенту приходится городить ad-hoc проверки. Лучше разделять:

  • field: null (поле существует, но значение неизвестно/не применимо),
  • или field отсутствует (поле нерелевантно) — но тогда это должно быть описано в схеме.

Пример: JSON schema на практике (без формализма, но строгостью)

Возьмём ресурс User:

Успешный ответ GET /v1/users/{id}:

  • 200:
code
{
  "id": "b7a5d8a0-2f6c-4d12-a0c0-5e4a0b9c1234",
  "email": "alex@example.com",
  "name": "Alex",
  "status": "ACTIVE",
  "createdAt": "2025-04-10T09:15:30Z",
  "updatedAt": "2026-06-01T11:00:00Z"
}

Контракт должен уточнить:

  • status ∈ {ACTIVE, SUSPENDED, DELETED};
  • createdAt/updatedAt всегда есть и всегда ISO 8601;
  • name nullable или всегда строка.

Версионирование API: как не превратить “v2” в костыль

Типы версионирования

Есть три распространённых подхода:

  1. URI versioning: /v1/..., /v2/...
    Плюсы: простота кэширования, ясность. Минусы: разрастание путей.
  2. Header versioning: Accept: application/vnd.company.api.v1+json
    Плюсы: чистые пути, версия отделена от ресурса. Минусы: сложнее для тестов и документации.
  3. Query versioning: ?version=1
    Плюсы: удобно. Минусы: чаще ломает кэширование и становится хаотичным.

Для REST контрактов чаще выбирают URI или Accept header, а критическое правило одно: версия должна защищать совместимость.

Как фиксировать совместимость: “breaking change” и “non-breaking change”

  • Non-breaking изменения:

    • добавили новое поле в ответ (если клиенты игнорируют неизвестное поле);
    • добавили новые варианты enum (если клиенты не делают жёсткий switch без default);
    • добавили новый тип ошибки (если клиенты обрабатывают “unknown type”).
  • Breaking изменения:

    • изменили тип поля (string → int);
    • изменили значение enum существующего варианта;
    • удалили поле (кроме случаев, когда контракт это допускает);
    • изменили семантику статусов/ошибок;
    • поменяли формат дат/валют.

Важно: иногда изменение “только текста ошибки” тоже может считаться breaking, если клиент парсит message. Поэтому лучше просить клиентов опираться на code/type, а не на текст.

Практическая стратегия эволюции контракта

  1. Согласовать ошибки заранее и держать стабильно.
  2. Новые поля — add-only (с объяснением условий появления).
  3. Для enum добавляйте значения аккуратно и тестируйте клиенты с “default”.
  4. Для удаления поля — deprecate (пометить устаревшим) и через релизы убрать.

Как писать контракт: структура документа и “что обязательно описать”

Что включить в контракт для каждого endpoint

Для каждого метода API документируйте:

  1. Method + path
  2. Назначение (1–2 предложения)
  3. Authentication (требуется/нет, схема)
  4. Запрос:
    • параметры (path/query) с типами;
    • body — схема;
    • правила idempotency (если есть).
  5. Ответы:
    • список статусов;
    • схема тела для каждого успеха;
    • схема ошибки для 400/401/403/404/409/500.
  6. Побочные эффекты:
    • создаёт/меняет состояние;
    • идемпотентность POST (иногда делают через Idempotency-Key).
  7. Ограничения:
    • лимиты, требования к размеру полей;
    • rate limits (обычно 429).

Чеклист контрактного дизайнера

  • Есть ли единый формат ошибок?
  • Клиент может однозначно понять “что не так” (по type/code/field)?
  • Для каждого сценария определён правильный HTTP статус?
  • Для 201 есть Location или чёткое объяснение, почему нет?
  • Поля ответов стабильны: типы, nullability, даты.
  • Версионирование продумано: какие изменения будут breaking?
  • Есть пример запрос/ответ (минимум один успешный и один ошибочный).

Примеры запросов/ответов: как показать контракт “в действии”

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

Пример 1: создание ресурса с валидацией

Запрос

code
POST /v1/users
Content-Type: application/json
Authorization: Bearer eyJ...

{
  "email": "bad-email",
  "name": ""
}

Ответ

code
HTTP/1.1 400 Bad Request
Content-Type: application/json
code
{
  "type": "https://api.example.com/problems/validation-error",
  "title": "Validation error",
  "status": 400,
  "detail": "Request body contains invalid fields.",
  "instance": "6b1c9f8a-1f6c-4b6c-9b52-2f7d4c4b9f3f",
  "errors": [
    {
      "field": "email",
      "code": "INVALID_FORMAT",
      "message": "Email has an invalid format."
    },
    {
      "field": "name",
      "code": "MIN_LENGTH",
      "message": "Name must not be empty."
    }
  ]
}

Контрактное уточнение, которое важно: клиент знает, что ошибки приходят в массиве errors и каждую можно привязать к полю.

Пример 2: конфликт при уникальности

Запрос

code
POST /v1/users
Content-Type: application/json

{
  "email": "alex@example.com",
  "name": "Alex"
}

Если email уникален, то повторный запрос может конфликтовать.

Ответ

code
HTTP/1.1 409 Conflict
Content-Type: application/json
code
{
  "type": "https://api.example.com/problems/conflict",
  "title": "Resource conflict",
  "status": 409,
  "detail": "User with given email already exists.",
  "instance": "2f5c8a9e-77b3-44ac-9f8a-0bb3d2aef7cc",
  "errors": [
    {
      "field": "email",
      "code": "DUPLICATE",
      "message": "This email is already registered."
    }
  ]
}

Здесь ключ — 409, а не “200 с флагом”. Клиент по коду/типу ошибки может корректно показать форму (“email уже занят”) и не пытаться “создавать заново”.

Пример 3: идемпотентный POST (если у вас платежи/создания с ретраями)

Контракт часто ломается при ретраях: сеть отвалилась, клиент не знает, создался ли ресурс. Решение — идемпотентность через заголовок.

Запрос

code
POST /v1/payments
Idempotency-Key: 2d5b6c1a-0b7f-4c3d-98d3-3f6d2c4d7a90
Content-Type: application/json
Authorization: Bearer eyJ...

{
  "orderId": "ORD-100500",
  "amount": 1999,
  "currency": "RUB"
}

Ответы (варианты контракта)

  • Если запрос обрабатывается впервые:
code
HTTP/1.1 201 Created
Location:

Войдите, чтобы поставить лайк и оставить комментарий.

Автор

$ sudo teach IT

Продолжите обучение

Все курсы
Python – для начинающих!

Python – для начинающих!

С нуля до профессионального уровня. Подходит для всех. Учитесь каждый день и овладейте самым популярным языком программирования.

Перейти к курсу
Приложения для iPhone и Apple Watch на SwiftUI

Приложения для iPhone и Apple Watch на SwiftUI

Разработка приложений для iPhone и Apple Watch на SwiftUI: навигация, SwiftData, виджеты, часы, выпуск. Нужен Mac с Xcode 27, сами устройства не нужны.

Перейти к курсу
Ботостроение Telegram

Ботостроение Telegram

Лёгкий, быстрый и доступный способ познакомиться с миром ботостроения в Telegram. Видео, конспекты, практика и помощь – всё у нас на курсе.

Перейти к курсу
Приложения для macOS на SwiftUI

Приложения для macOS на SwiftUI

Разработка приложений для Mac на SwiftUI: окна и меню, Liquid Glass, SwiftData, сеть, выпуск. Нужен Mac с macOS 27 и Xcode 27.

Перейти к курсу

Другие статьи

Как читать чужой код: техники и привычки опытных разработчиков
читать

Как читать чужой код: техники и привычки опытных разработчиков

Делимся практическими приёмами, которые помогают быстро разобраться в незнакомой кодовой базе — от построения mental map до использования отладчика и git blame. Полезно всем уровням от junior до senior.

17 июля 2026 г.
670
Как писать эффективные SQL-запросы под доменную модель: меньше JOIN’ов, больше ясности
писать

Как писать эффективные SQL-запросы под доменную модель: меньше JOIN’ов, больше ясности

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

25 июля 2026 г.
630
Создание Telegram бота в 2026 легко и просто! Полный курсы!
создание

Создание Telegram бота в 2026 легко и просто! Полный курсы!

19 июня 2026 г.
1421
Как выбрать, куда идти в IT: веб, боты, данные — без хаоса
выбрать

Как выбрать, куда идти в IT: веб, боты, данные — без хаоса

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

5 октября 2026 г.
580
Путь от README до демо: как собрать портфолио разработчика, чтобы его хотели смотреть
путь

Путь от README до демо: как собрать портфолио разработчика, чтобы его хотели смотреть

Разберём структуру репозитория, как упаковать проект в понятную историю (проблема → решение → запуск → метрики качества) и какие артефакты повышают доверие: скринкасты, архитектурная схема, чек-лист продакшн-готовности.

17 сентября 2026 г.
1070
Как понять, какой язык программирования учить первым: простой тест и выбор
понять

Как понять, какой язык программирования учить первым: простой тест и выбор

Помогу новичку выбрать первый язык без стресса: объясню варианты по целям (веб, боты, анализ данных), что учить в первую очередь и как не потратить месяцы впустую. В конце — краткий план старта на ближайшие недели.

2 октября 2026 г.
620

Комментарии

Станьте первым в обсуждении

Поделитесь мнением о статье или задайте вопрос автору

Содержание

Что такое “контракт” REST API и почему он важнее “эндпоинтов”HTTP статусы: как читать и назначать коды правильноКак читать контракт по статусамБазовая матрица статусов (и самые частые ошибки)Практический шаблон: какие статусы должны быть для каждого кейсаФормат ошибок: единообразие вместо “разных сообщений”Что должен содержать error responseПример: согласованный формат ошибокОшибка как “контракт”: статусы + типы ошибок + правилаСхемы ответов: как проектировать структуру данных так, чтобы её не “ломали” релизыМинимальный набор правил для схемПример: JSON schema на практике (без формализма, но строгостью)Версионирование API: как не превратить “v2” в костыльТипы версионированияКак фиксировать совместимость: “breaking change” и “non-breaking change”Практическая стратегия эволюции контрактаКак писать контракт: структура документа и “что обязательно описать”Что включить в контракт для каждого endpointЧеклист контрактного дизайнераПримеры запросов/ответов: как показать контракт “в действии”Пример 1: создание ресурса с валидациейПример 2: конфликт при уникальностиПример 3: идемпотентный POST (если у вас платежи/создания с ретраями)