Три команды независимо написали свои части одного 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
контракт одного сервиса
IETF RFC
OpenAPI Specification
Company API Guidelines
Project API Style Guide
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 берёт значение из пути, приводит его к 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 должно исчезнуть или получить значение по умолчанию: ресурс заменяется целиком.
OLD RESOURCE
PUTновое представление целиком
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, потому что удалять уже нечего. Идемпотентность говорит о состоянии сервера, а не о коде ответа.
RESOURCE
DELETE
∅
28Два разных свойства HTTP-методов
Safe и idempotent часто путают. Нажимайте одну кнопку несколько раз подряд и следите за состоянием сервера.
Safe — клиент не просит изменить состояние сервера: GET, HEAD, OPTIONS.
Idempotent — повтор идентичной операции даёт тот же предполагаемый эффект, что один вызов: PUT, DELETE, GET.
Method
Safe
Idempotent
GET
YES
YES
POST
NO
NO
PUT
NO
YES
DELETE
NO
YES
POST в общем случае не идемпотентен: каждый повтор создаёт ещё одно обращение.
292xx — запрос обработан успешно
В нашем проекте понадобятся три успешных кода.
200OK
Запрос выполнен, результат в теле. GET, PATCH.
201Created
Создан новый ресурс, адрес в Location. POST.
204No Content
Выполнено, тело не нужно. DELETE.
Не следует автоматически возвращать 200 из любого endpoint. Status code должен описывать фактический результат операции: клиент по нему решает, что делать дальше, не разбирая тело.
Метод
Успешный код
GET
200
POST
201
DELETE
204
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.
CLIENTполучает 500 и публичное описание ошибки
SERVER
LOGStraceback и технические подробности
Внутренние детали должны оставаться на сервере: по ним злоумышленник узнаёт устройство системы.
32Формат body тоже является частью контракта
Одни и те же байты можно прочитать по-разному. Заголовок Content-Type сообщает, как интерпретировать тело.
в request
Content-Type: application/json
Сообщает серверу формат отправленного тела.
в response
Content-Type: application/json
Сообщает клиенту формат ответа.
для ошибок
Content-Type: application/problem+json
Этот формат стандартизированных ошибок разберём позже в этой теме.
BODY
MEDIA TYPE
INTERPRETABLE MESSAGEсообщение, которое программа может разобрать
33Стиль JSON необходимо зафиксировать
Команда выбирает единый стиль имён и применяет его последовательно во всех ресурсах.