APIСтандартизация
тема 1
Открыть слайдами →

16Без правил каждый разработчик создаёт свой язык

Три команды независимо написали свои части одного API. Все три endpoint работают.

До стандартизации
GET  /getUsers

POST /user/create

POST /delete-user?id=10
После стандартизации
GET    /users
POST   /users
DELETE /users/{user_id}

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

После стандартизации клиент может предугадать поведение API.

17API регулируется не одним документом

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

УровеньЧто определяет
IETF RFCсемантику HTTP: методы, коды, заголовки
OpenAPI Specificationспособ машинного описания API
Company API Guidelinesправила именования и совместимости в компании
Project API Style Guideдоговорённости конкретной команды
OpenAPI contractконтракт одного сервиса
  1. IETF RFC
  2. OpenAPI Specification
  3. Company API Guidelines
  4. Project API Style Guide
  5. OpenAPI contract конкретного сервиса

18Метод нельзя выбирать случайно

Начнём с нижнего уровня пирамиды — стандартов IETF. HTTP уже определяет назначение стандартных методов.

RFC 9110 «HTTP Semantics» описывает, в частности, методы:

GET
HEAD
POST
PUT
DELETE
OPTIONS

и их свойства — например, safe и idempotent.

Значение GET, POST или DELETE является частью контракта.

rfc-editor.org · RFC 9110СКРИНШОТ · 15.09.2026
RFC 9110, раздел 9 «Methods»: обзор и таблица стандартных методов со ссылками на подразделы. Клик — увеличить.

19URI описывает существительное

Действие уже выражается HTTP method. Если записать его ещё и в адрес, оно окажется в контракте дважды.

Плохо
/getTickets
/createTicket
/deleteTicket
Хорошо
/tickets
/users
/orders
/products
лучше
POST /tickets
хуже
POST /createTicket

20Один ресурс и набор ресурсов

Коллекция и элемент коллекции имеют разные адреса, но общий корень.

ЧтоЗапрос
КоллекцияGET /tickets
Конкретный ресурсGET /tickets/{ticket_id}
Вложенный ресурсGET /users/{user_id}/tickets

Вложенный адрес читается как «обращения пользователя с этим идентификатором».

Чрезмерно глубокие URI усложняют контракт: /companies/{id}/departments/{id}/users/{id}/tickets заставляет клиента знать всю цепочку, чтобы получить одно обращение.

/tickets
   │
   ├─ /1
   ├─ /2
   └─ /3

21Path parameter идентифицирует ресурс

Часть пути, которая меняется от ресурса к ресурсу, называется path parameter.

GET /tickets/42
/tickets/42
         ↑
    ticket_id

42 — часть адреса конкретного ресурса. Без неё запрос обращён уже к коллекции.

FastAPI
@app.get("/tickets/{ticket_id}")
def get_ticket(ticket_id: int):
    ...

FastAPI берёт значение из пути, приводит его к int и передаёт в функцию. Если в пути abc, клиент получит ошибку валидации, а функция не будет вызвана.

22Query parameter изменяет выборку

Query parameters записываются после знака «?» и уточняют запрос к тому же ресурсу.

Их удобно использовать для:

  • фильтрации;
  • сортировки;
  • поиска;
  • пагинации.
GET /tickets?status=open&limit=20

Основной ресурс остаётся тем же — /tickets, а параметры уточняют, какую часть коллекции вернуть:

ПараметрСмысл
status=openтолько открытые обращения
limit=20не больше 20 штук

23GET получает представление ресурса

GET используется для чтения. Он не должен менять состояние сервера.

  • GET/ticketsколлекция
  • GET/tickets/42один ресурс

Запрос GET /api/v1/tickets/42 может вернуть:

200OK

обращение найдено, оно в теле ответа

404Not Found

обращения с таким идентификатором нет

свойство метода

GET относится к safe HTTP methods

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

CLIENT→← DATA ←→SERVER

24POST создаёт ресурс или запускает операцию

Для создания обращения клиент отправляет POST на коллекцию.

request
POST /tickets
body
{
  "title": "Не работает VPN",
  "description": "Ошибка подключения"
}

При успешном создании сервер отвечает:

response
201 Created
Location: /tickets/{new_id}

Идентификатор нового ресурса формирует сервер, поэтому клиент узнаёт адрес созданного обращения из заголовка Location.

POST /tickets→NEW TICKET

25PUT заменяет состояние ресурса

PUT используется, когда клиент передаёт полное новое представление ресурса.

request
PUT /tickets/42

{
  "title": "Не работает VPN",
  "description": "Ошибка подключения",
  "status": "closed"
}

Поле, которое клиент не передал, по смыслу PUT должно исчезнуть или получить значение по умолчанию: ресурс заменяется целиком.

  1. OLD RESOURCE
  2. PUTновое представление целиком
  3. NEW RESOURCE

По HTTP semantics PUT идемпотентен: повторение идентичного PUT даёт тот же предполагаемый эффект, что и один вызов.

26PATCH изменяет часть ресурса

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

request
PATCH /tickets/42

{
  "status": "closed"
}

В нашем учебном API для редактирования Ticket используем PATCH.

Ticket
├── title
├── description
└── status  ← PATCH

Так клиент не затрёт поле, которое за это время изменил другой пользователь, и отправляет меньше данных.

27DELETE удаляет ресурс

После успешного удаления возвращать нечего, поэтому ответ не содержит тела.

request
DELETE /tickets/42
response
204 No Content

HTTP определяет DELETE как идемпотентный по предполагаемому эффекту: после первого и после пятого одинакового запроса ресурса нет.

Код ответа при повторе может отличаться: второй DELETE вернёт 404, потому что удалять уже нечего. Идемпотентность говорит о состоянии сервера, а не о коде ответа.

  1. RESOURCE
  2. DELETE
  3. ∅

28Два разных свойства HTTP-методов

Safe и idempotent часто путают. Нажимайте одну кнопку несколько раз подряд и следите за состоянием сервера.

Safe — клиент не просит изменить состояние сервера: GET, HEAD, OPTIONS.

Idempotent — повтор идентичной операции даёт тот же предполагаемый эффект, что один вызов: PUT, DELETE, GET.

MethodSafeIdempotent
GETYESYES
POSTNONO
PUTNOYES
DELETENOYES

POST в общем случае не идемпотентен: каждый повтор создаёт ещё одно обращение.

    292xx — запрос обработан успешно

    В нашем проекте понадобятся три успешных кода.

    200OK

    Запрос выполнен, результат в теле. GET, PATCH.

    201Created

    Создан новый ресурс, адрес в Location. POST.

    204No Content

    Выполнено, тело не нужно. DELETE.

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

    МетодУспешный код
    GET200
    POST201
    DELETE204

    304xx — проблема находится на стороне запроса

    Коды 4xx сообщают клиенту: повторять тот же запрос бесполезно, его нужно исправить.

    400Bad Request

    Запрос нельзя разобрать: например, сломанный JSON.

    401Unauthorized

    Требуется корректная аутентификация: токена нет или он недействителен.

    403Forbidden

    Клиент известен, но эта операция ему запрещена.

    404Not Found

    Ресурса с таким адресом нет.

    409Conflict

    Запрос противоречит текущему состоянию ресурса.

    422Unprocessable Content

    JSON корректен, но данные не проходят проверку схемы.

    429Too Many Requests

    Клиент превысил допустимую частоту запросов.

    401 и 403 имеют разный смысл: в первом случае сервер не знает, кто перед ним, во втором знает и отказывает.

    315xx — сервер не смог выполнить корректный запрос

    Коды 5xx означают, что запрос был корректным, но сервер не справился.

    500 Internal Server Error

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

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

    • Python traceback;
    • SQL query;
    • пароль;
    • filesystem path.
    1. CLIENTполучает 500 и публичное описание ошибки
    2. SERVER
    3. LOGStraceback и технические подробности

    Внутренние детали должны оставаться на сервере: по ним злоумышленник узнаёт устройство системы.

    32Формат body тоже является частью контракта

    Одни и те же байты можно прочитать по-разному. Заголовок Content-Type сообщает, как интерпретировать тело.

    в request
    Content-Type: application/json

    Сообщает серверу формат отправленного тела.

    в response
    Content-Type: application/json

    Сообщает клиенту формат ответа.

    для ошибок
    Content-Type: application/problem+json

    Этот формат стандартизированных ошибок разберём позже в этой теме.

    1. BODY
    2. MEDIA TYPE
    3. INTERPRETABLE MESSAGEсообщение, которое программа может разобрать

    33Стиль JSON необходимо зафиксировать

    Команда выбирает единый стиль имён и применяет его последовательно во всех ресурсах.

    наш проект: snake_case
    {
      "ticket_id": 42,
      "created_at": "2026-09-15T17:30:00Z"
    }

    Другая организация может стандартизировать camelCase. Ошибкой будет не выбор стиля, а смешение: created_at в одном ответе и updatedAt в соседнем.

    СтильПример
    snake_casecreated_at
    camelCasecreatedAt

    Выбрать один стиль. Главное — единообразие внутри API ecosystem.

    Кадр крупным планом