коротко

API — это способ одной программы попросить что-то у другой, не зная, как та устроена внутри. REST — самый распространённый стиль таких запросов поверх HTTP: данные представлены как ресурсы (заказы, пользователи), у каждого свой адрес (URL), а действия задаются HTTP-методами: GET — прочитать, POST — создать, PUT/PATCH — изменить, DELETE — удалить. Ответ всегда несёт код: 2xx — успех, 4xx — ошибка клиента, 5xx — ошибка сервера. Контракт этого API аналитик описывает в OpenAPI (бывший Swagger) — машиночитаемой спецификации, по которой бэк, фронт и тесты сверяют ожидания.

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

Что такое API и зачем он вообще

Представьте розетку. Вы не знаете, как устроена электростанция, какие там турбины и провода. Вам важно одно: есть стандартный разъём, в него можно воткнуть вилку и получить ток. API (Application Programming Interface) — это такая же розетка между программами. Один сервис предоставляет набор «разъёмов» — что можно попросить и что в ответ придёт, — а как он считает результат внутри, никого не касается.

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

REST: ресурсы, URL и HTTP-методы

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

Адрес — это URL. Соглашение простое: существительные во множественном числе, без глаголов.

/orders          — коллекция всех заказов
/orders/123      — конкретный заказ с id 123
/orders/123/items — позиции внутри заказа 123

Глагол прячется не в URL, а в HTTP-методе. Один и тот же адрес /orders/123 ведёт себя по-разному в зависимости от метода:

  • GET — прочитать. «Дай мне заказ 123». Ничего не меняет, можно повторять сколько угодно.
  • POST — создать. «Создай новый заказ» (отправляется на /orders, сервер сам выдаёт id).
  • PUT — заменить целиком. «Вот полная новая версия заказа 123».
  • PATCH — изменить частично. «В заказе 123 поменяй только статус».
  • DELETE — удалить. «Убери заказ 123».

Метод — это и есть намерение

Когда пишете требование, не говорите «ручка для заказа». Говорите «GET /orders/{id} — получить заказ», «POST /orders — создать». Связка метод + URL однозначно описывает действие, и разработчик понимает вас без переспросов.

Из чего состоит запрос и ответ

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

Эндпоинт — это конкретная точка входа, пара «метод + URL», например POST /orders. Сколько у API эндпоинтов — столько у него умений.

Заголовки (headers) — служебная информация о запросе: в каком формате тело (Content-Type: application/json), кто отправитель (токен авторизации — см. OAuth2 и JWT), что клиент готов принять. Это как поля на конверте, а не само письмо.

Тело (body) — сами данные, обычно в формате JSON. У GET и DELETE тела обычно нет (нечего передавать), у POST/PUT/PATCH — есть.

POST /orders
Content-Type: application/json
Authorization: Bearer eyJhbGci...

{
  "userId": 42,
  "items": [{ "sku": "A-100", "qty": 2 }],
  "comment": "позвонить за час"
}

В ответ сервер присылает заголовки, тело и — самое важное — код состояния.

Коды ответов: 2xx, 4xx, 5xx

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

  • 2xx — успех. 200 OK (прочитали), 201 Created (создали), 204 No Content (сделали, отвечать нечем). На POST обычно приходит 201 Created — но иногда 200, если ресурс не создаётся, а просто выполнено действие (или повтор идемпотентного запроса возвращает уже созданный результат).
  • 4xx — виноват клиент. 400 Bad Request (кривой запрос), 401 Unauthorized (не представился), 403 Forbidden (представился, но нельзя), 404 Not Found (ресурса нет), 409 Conflict (конфликт состояния).
  • 5xx — виноват сервер. 500 Internal Server Error (что-то сломалось внутри), 503 Service Unavailable (сервис недоступен).

Самая частая дыра в требованиях

Описать «счастливый путь» (200 — всё хорошо) легко. А что вернётся, если товара нет на складе? Если у пользователя нет прав? Если он повторил запрос дважды? Хороший аналитик прописывает коды и тела ошибок наравне с успехом — иначе разработчик придумает их сам, и каждый по-своему.

401 и 403 — это разные ответы

Их путают чаще всего. 401 Unauthorized — «я тебя не узнал»: токен не пришёл, протух или невалиден. Лечится повторным входом. 403 Forbidden — «я тебя узнал, но сюда тебе нельзя»: ты вошёл, но прав на этот ресурс нет, и повторный вход не поможет. Отдельный нюанс: иногда на чужой ресурс осознанно возвращают 404 вместо 403 — чтобы посторонний даже не узнал, что такой объект существует. Кто и что может — это уже авторизация (RBAC/ABAC).

Идемпотентность методов: какой повтор безопасен

Сеть ненадёжна: ответ может потеряться по дороге, клиент не дождётся и пошлёт запрос ещё раз. Поэтому у каждого метода важно знать, что будет от повтора. Метод идемпотентен, если повторить его сколько угодно раз — результат тот же, что от одного раза.

  • GET — идемпотентен и ничего не меняет: читать заказ 123 хоть сто раз — состояние то же.
  • PUT — идемпотентен: «заказ 123 = вот этот объект»; повторишь — перезапишешь тем же, состояние не сдвинется.
  • DELETE — идемпотентен по состоянию: после первого удаления заказа его нет, и второй DELETE оставляет его таким же отсутствующим (хотя код может прийти 404 — а состояние то же).
  • PATCH — в общем случае не гарантирован: «увеличь баланс на 100» при повторе прибавит ещё 100. Идемпотентен, только если описан как «установи значение X», а не «измени на дельту».
  • POSTне идемпотентен: каждый повтор создаёт новый ресурс. Два POST /orders — два заказа, два списания.

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

Коллекции: пагинация, фильтрация, сортировка

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

GET /orders?status=paid&sort=-createdAt&limit=20&offset=40

Что здесь заложено: фильтрация (?status=paid — только оплаченные), сортировка (?sort=-createdAt — сначала новые, минус = по убыванию), пагинация (limit — сколько отдать за раз, offset — сколько пропустить). Пагинация бывает двух видов: offset/limit (проще, но «прыгает», если в момент листания добавились записи) и cursor (курсор-указатель на последнюю отданную запись — стабильнее на больших и живых списках). По-бытовому: offset/limit — это «листаем книгу по 20 страниц, а offset — сколько страниц уже пролистали»; cursor — «закладка на последней прочитанной странице: она не собьётся, даже если в книгу вклеили новый лист». Почему offset плывёт на живом списке: если между двумя запросами кто-то вставил запись, offset=40 сдвигается — и одна и та же строка либо покажется дважды, либо проскочит незамеченной. Cursor указывает на конкретную запись («дай то, что идёт после заказа №577»), поэтому от вставок не плывёт. В требованиях к коллекции аналитик называет: какие фильтры поддерживаются, по каким полям сортировка, размер страницы по умолчанию и максимум, и в каком формате возвращается общее число и признак «есть ли ещё».

200 + пустой список против 404 — спор на три дня

Тот самый случай из начала записи. Правило простое: запрос коллекции, под которую ничего не нашлось, — это успех, потому что ресурс-коллекция существует, просто он пуст: 200 OK и тело []. А вот запрос конкретного ресурса по id, которого нет, — это 404 Not Found: запрошенного объекта не существует. То есть GET /orders?status=paid без результатов → 200 + []; GET /orders/999, где такого заказа нет → 404. Если бы у нас тогда был контракт с этим правилом, спор «работает или нет» не случился бы: пустой список — это валидный ответ, а не поломка.

Как это выглядит во времени

sequenceDiagram
  participant C as Клиент
  participant S as Сервер (API)
  participant DB as База данных
  C->>S: POST /orders {userId, items}
  S->>S: проверить запрос (валидация)
  S->>DB: сохранить заказ
  DB-->>S: id = 123
  S-->>C: 201 Created {id: 123, status: "new"}

Схема выше — обычный цикл «запрос-ответ» при создании заказа. Клиент отправляет POST на /orders с телом в JSON. Сервер сначала проверяет запрос (все ли поля на месте, корректны ли значения) — если нет, он сразу вернёт 400 и до базы дело не дойдёт. Если всё в порядке, сервер сохраняет заказ в базу, получает идентификатор и возвращает клиенту код 201 Created вместе с телом, где лежит id и начальный статус. Главное, что отсюда нужно вынести: ответ всегда есть, и его форму — и для успеха, и для ошибки — задаёт контракт.

OpenAPI и Swagger: зачем аналитику описывать контракт

Пока контракт живёт в головах, повторяется моя история с трёхдневным спором. OpenAPI — это формат, в котором контракт API записывают так, чтобы его понимали и люди, и машины. Это обычный текстовый файл (YAML или JSON), где перечислены все эндпоинты, их параметры, тела запросов и ответов, коды.

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

paths:
  /orders/{id}:
    get:
      summary: Получить заказ по id
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: Заказ найден
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Order"
        "404":
          description: Заказ не найден

Этот фрагмент описывает один эндпоинт: GET /orders/{id}. Видно, что id — обязательный параметр в пути и должен быть целым числом; что при успехе вернётся объект Order (его схема описана отдельно и подключена через $ref, чтобы не дублировать), а при отсутствии заказа — код 404. По такому описанию разработчик соберёт ручку, а тестировщик — проверки, не задавая вам ни одного уточняющего вопроса.

Откуда это взялось

REST описал Рой Филдинг в 2000 году — в своей докторской диссертации, где он сформулировал архитектурный стиль, на котором уже фактически работал веб. До REST интеграции делали через SOAP и XML-RPC: тяжёлые конверты из XML, отдельные описания на WSDL, много церемоний. REST победил простотой — он не придумывал новый протокол, а опирался на уже существующий HTTP. OpenAPI вырос из проекта Swagger, который Тони Тэм сделал около 2010 года как инструмент описания и документирования API. В 2015-м спецификацию передали в OpenAPI Initiative и переименовали — поэтому «Swagger» и «OpenAPI» в разговоре часто значат одно и то же.

Когда REST — не лучший выбор

REST хорош, когда взаимодействие — это «попросил и сразу жду ответ». Но не всё в жизни так: иногда задача долгая, иногда событие нужно разослать многим, иногда сервисы должны общаться, не дожидаясь друг друга. Тогда поверх или вместо REST появляются очереди, события и вебхуки — это уже разговор про синхронную и асинхронную интеграцию. А если клиенту нужно гибко выбирать, какие поля получать, — смотрят в сторону GraphQL. Но пока запомните REST как базовый словарь: на нём говорит большинство API, с которыми вы столкнётесь.

Версионирование: почему ломающее изменение требует новой версии

Контракт живёт годами, а потребителей у него много — фронт, мобилка, чужие интеграции. Пока вы добавляете необязательное поле в ответ — старые клиенты его просто не замечают, это обратно совместимое изменение. Но если вы переименовали поле, удалили его, сделали раньше необязательное обязательным или поменяли тип — это ломающее (breaking) изменение: старый клиент перестанет понимать ответ. Менять контракт «на месте» нельзя — выкатывают новую версию, а старая живёт, пока на ней есть потребители.

Способов проставить версию три: в пути URL (/v1/orders, /v2/orders) — самый частый и наглядный; в заголовке (Accept-Version: 2) — URL остаётся чистым; через media-type в Accept (application/vnd.company.v2+json) — академичнее и реже на практике. Аналитику важнее не выбор синтаксиса, а правило: ломающее изменение = новая версия + план отказа от старой. Глубже эту тему разбираем в записи про версионирование API; та же логика обратной совместимости работает и в gRPC с protobuf.

«А это точно REST?»: уровни зрелости Ричардсона

На собесе любят спросить: «ваш API — точно RESTful?». За этим стоит модель зрелости Ричардсона — четыре уровня того, насколько API следует идеям REST. Уровень 0 — один эндпоинт, всё через POST (по сути RPC поверх HTTP). Уровень 1 — появились ресурсы со своими URL. Уровень 2 — правильно используются HTTP-методы и коды ответов (GET читает, POST создаёт, 201/404/409 на своих местах). Уровень 3 — HATEOAS: в ответе приходят ссылки на возможные следующие действия, и клиент ходит по ним, как по веб-страницам.

Честная правда: подавляющее большинство «REST API» в проде — это уровень 2, и это нормально. HATEOAS на практике почти не встречается — он сложен и редко окупается. Так что когда вам говорят «у нас REST», обычно имеют в виду «ресурсы + методы + коды», а не гипермедиа. Знать термин стоит ровно для одного: не растеряться, когда на собеседовании спросят «а строго по Филдингу это REST?» — спокойно ответить «по Ричардсону это уровень 2, до HATEOAS мы намеренно не доходим».

Как это спрашивают на собесе

REST — тема №1 на собеседовании аналитика, заходы предсказуемы. «В чём разница PUT и PATCH? POST и PUT?» — PUT заменяет ресурс целиком, PATCH меняет часть полей; POST создаёт новый ресурс (сервер выдаёт id), PUT кладёт/заменяет ресурс по известному адресу. «Какие методы идемпотентны?» — GET, PUT, DELETE (повтор не меняет итогового состояния); POST — нет, PATCH — только если задаёт значение, а не дельту. «Что вернёте, если создаваемый ресурс уже существует?» — 409 Conflict; а чтобы повтор POST по сети не создал дубль — идемпотентный ключ в заголовке, по которому сервер вернёт тот же результат. «Чем 401 отличается от 403?» — 401 «не узнал тебя» (нет/протух токен), 403 «узнал, но нельзя» (нет прав). «Пустой результат — это 404?» — для коллекции нет: 200 + []; 404 — только для конкретного ресурса по id, которого нет. Маркер мидла — сами назвать пагинацию/фильтры на коллекции и версионирование при ломающем изменении.

Частые вопросы

Что такое REST API простыми словами?

REST API — это способ программам общаться по HTTP, где всё представлено как ресурсы (заказы, пользователи) со своими адресами-URL, а действия задаются методами: GET читает, POST создаёт, PUT/PATCH меняет, DELETE удаляет. На каждый запрос сервер возвращает код состояния (2xx — успех, 4xx — ошибка клиента, 5xx — ошибка сервера) и обычно тело в формате JSON. Это самый распространённый стиль API в вебе.

В чём разница между PUT и PATCH?

PUT заменяет ресурс целиком — вы присылаете полную новую версию объекта, и она затирает старую. PATCH меняет ресурс частично — вы присылаете только те поля, которые нужно поменять, остальное остаётся как было. Если надо обновить один статус заказа, логичнее PATCH; если перезаписать весь объект — PUT.

Зачем аналитику OpenAPI / Swagger?

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