Форматы ответов API: лучшие практики для согласованных API
Несогласованные ответы API — одна из главных жалоб фронтенд-разработчиков. Когда каждый эндпоинт возвращает данные в разной структуре, клиентский код обрастает множеством частных случаев. Единый формат ответа улучшает опыт разработчика, сокращает количество ошибок и делает ваш API самодокументируемым.
Обёртка ответа
Оборачивайте каждый ответ в единообразную структуру:
Успешный ответ (единичный ресурс)
{
"data": {
"id": "user_123",
"name": "Alice",
"email": "alice@example.com",
"createdAt": "2024-01-15T10:30:00Z"
},
"meta": {
"requestId": "req_abc123"
}
}
Успешный ответ (коллекция)
{
"data": [
{ "id": "user_123", "name": "Alice" },
{ "id": "user_456", "name": "Bob" }
],
"meta": {
"total": 142,
"page": 1,
"perPage": 20,
"requestId": "req_def456"
}
}
Ошибка
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": [
{ "field": "email", "message": "Invalid email format" }
]
},
"meta": {
"requestId": "req_ghi789"
}
}
Ключевой принцип: клиент всегда проверяет наличие data или error на верхнем уровне. Никогда не смешивайте данные об успешном выполнении и информацию об ошибке в одном ответе.
Проверьте формат вашего ответа с помощью нашего JSON Валидатора.
Коды состояния HTTP
Используйте коды состояния правильно — это первое, что проверяет клиент:
Коды успеха
| Код | Когда использовать |
|---|---|
| 200 OK | Успешный GET, PUT/PATCH |
| 201 Created | POST, создавший ресурс |
| 204 No Content | Успешный DELETE (без тела ответа) |
Коды ошибок клиента
| Код | Когда использовать |
|---|---|
| 400 Bad Request | Некорректный синтаксис запроса |
| 401 Unauthorized | Отсутствует или недействительна аутентификация |
| 403 Forbidden | Аутентифицирован, но не авторизован |
| 404 Not Found | Ресурс не существует |
| 409 Conflict | Конфликт состояния ресурса (дубликат) |
| 422 Unprocessable | Синтаксис корректен, но семантические ошибки |
| 429 Too Many Requests | Превышен лимит запросов |
Коды ошибок сервера
| Код | Когда использовать |
|---|---|
| 500 Internal Server Error | Неожиданный сбой сервера |
| 502 Bad Gateway | Сбой вышестоящего сервиса |
| 503 Service Unavailable | Временная перегрузка или обслуживание |
Проектирование ответов об ошибках
Хорошие ответы об ошибках помогают разработчикам быстро находить причину проблемы:
{
"error": {
"code": "RESOURCE_NOT_FOUND",
"message": "User with ID 'user_999' not found",
"details": [],
"documentationUrl": "https://api.example.com/docs/errors#RESOURCE_NOT_FOUND"
},
"meta": {
"requestId": "req_xyz789",
"timestamp": "2024-01-15T10:30:00Z"
}
}
Правила:
code— машиночитаемый код (постоянная строка, не HTTP-статус)message— человекочитаемое сообщение (может измениться без нарушения работы клиентов)details— ошибки на уровне полей при провале валидацииrequestId— позволяет службе поддержки отследить запрос в логах
Шаблоны пагинации
На основе смещения
Простая реализация, поддерживает переход к произвольной странице:
{
"data": ["..."],
"meta": {
"page": 2,
"perPage": 20,
"total": 142,
"totalPages": 8
},
"links": {
"first": "/api/users?page=1&per_page=20",
"prev": "/api/users?page=1&per_page=20",
"next": "/api/users?page=3&per_page=20",
"last": "/api/users?page=8&per_page=20"
}
}
На основе курсора
Лучше подходит для данных в реальном времени и больших наборов данных:
{
"data": ["..."],
"meta": {
"hasNext": true,
"hasPrev": true
},
"links": {
"next": "/api/users?cursor=eyJpZCI6MTQzfQ&limit=20",
"prev": "/api/users?cursor=eyJpZCI6MTIzfQ&limit=20&direction=prev"
}
}
Дата и время
Всегда используйте ISO 8601 с указанием часового пояса:
{
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T14:22:33+00:00",
"expiresAt": "2024-12-31T23:59:59Z"
}
Чего следует избегать:
- Unix-временные метки (неоднозначно: секунды или миллисекунды?)
- Локальные даты без часового пояса
- Нестандартные форматы вроде MM/DD/YYYY
Подробнее о работе с временными метками читайте в нашем руководстве по Unix Timestamps.
Null против отсутствующих полей
Два распространённых подхода:
Включать со значением null (явный):
{ "name": "Alice", "avatar": null, "bio": null }
Опускать отсутствующие поля (разреженный):
{ "name": "Alice" }
Рекомендация: Будьте последовательны в рамках вашего API. Явные значения null лучше для типизированных языков (клиенты знают, что поле существует). Разреженный формат лучше для сред с ограниченной пропускной способностью.
Версионирование ответов
Когда формат ответа меняется, используйте версионирование API:
GET /api/v2/users/123
Ломающие изменения, требующие версионирования:
- Удаление поля
- Изменение типа поля
- Переименование поля
- Изменение структуры обёртки ответа
Неломающие изменения (безопасны без версионирования):
- Добавление новых полей
- Добавление новых эндпоинтов
- Добавление новых значений перечислений
FAQ
Стоит ли оборачивать успешные ответы в ключ data или возвращать ресурс напрямую?
Использование обёртки data обеспечивает единообразную структуру для всех ответов и оставляет место для метаданных, пагинации и ссылок рядом с данными. Прямой возврат ресурса проще для эндпоинтов с одним ресурсом. Большинство современных API используют подход с обёрткой ради согласованности.
Как обрабатывать частичные сбои в пакетных операциях?
Возвращайте 200 с ответом, который включает как успешные, так и неудачные результаты в объекте data. Использование 207 Multi-Status (WebDAV) — альтернативный вариант, но он реже применяется в REST API.
Связанные ресурсы
- JSON Форматирование — Форматирование ответов API
- Шаблоны проектирования JSON API — Полное руководство по проектированию API
- Валидация JSON Schema — Валидация структуры ответов