REST API Style Guide: Batch, async, локализация

REST API: batch-эндпоинты, асинхронные операции с polling/webhook, локализация ответов, обработка timezone и валют — практические примеры.

Статья внедрена в скилл AI-агента ucp-api-review / ucp-api-design REST API batch async

REST API batch async: 17. Batch-операции

17.1. Эндпоинт

REST API batch async — массовые операции оформляются как POST /resources/batch:

POST /api/v1/orders/batch
POST /api/v1/notifications/batch/send

17.2. Формат запроса

Тело содержит массив элементов:

{
  "items": [
    { "productId": "aaa", "quantity": 2 },
    { "productId": "bbb", "quantity": 1 },
    { "productId": "ccc", "quantity": 5 }
  ]
}

17.3. Ответ с частичными ошибками

Код 200 OK (даже если часть элементов не прошла). Тело содержит результат по каждому элементу:

{
  "results": [
    { "index": 0, "status": "SUCCESS", "orderId": "..." },
    { "index": 1, "status": "ERROR", "error": { "code": "INSUFFICIENT_STOCK", "detail": "Товар bbb отсутствует на складе" } },
    { "index": 2, "status": "SUCCESS", "orderId": "..." }
  ],
  "summary": {
    "total": 3,
    "succeeded": 2,
    "failed": 1
  }
}
  • index -- позиция элемента в исходном массиве (0-based)
  • status -- SUCCESS или ERROR
  • При ERROR -- объект error с code и detail (упрощённый формат, не полный ProblemDetails, т.к. ошибка относится к элементу batch, а не к HTTP-запросу)
  • summary -- агрегация: total (всего элементов), succeeded (успешных), failed (с ошибкой)

17.4. Ограничения

  • Максимальный размер batch указывается в документации (например, 100 элементов)
  • При превышении -- 400 Bad Request с code: BATCH_SIZE_EXCEEDED

18. Длительные операции (async)

18.1. Паттерн polling

Для операций, которые не могут завершиться за время HTTP-запроса:

  1. Клиент отправляет запрос
  2. Сервер возвращает 202 Accepted с ссылкой на задачу
  3. Клиент периодически опрашивает задачу до завершения
POST /api/v1/reports/generate
Content-Type: application/json

{ "dateFrom": "2025-01-01", "dateTo": "2025-12-31" }

Ответ:

HTTP/1.1 202 Accepted
Location: /api/v1/tasks/550e8400-e29b-41d4-a716-446655440000

{
  "taskId": "550e8400-e29b-41d4-a716-446655440000",
  "status": "PENDING",
  "createdAt": "2025-03-15T10:30:00Z",
  "statusUrl": "/api/v1/tasks/550e8400-e29b-41d4-a716-446655440000"
}

18.2. Опрос статуса задачи

GET /api/v1/tasks/{id}

Пока задача выполняется:

{
  "taskId": "550e8400-e29b-41d4-a716-446655440000",
  "status": "PROCESSING",
  "progress": 45,
  "createdAt": "2025-03-15T10:30:00Z"
}

После завершения:

{
  "taskId": "550e8400-e29b-41d4-a716-446655440000",
  "status": "COMPLETED",
  "progress": 100,
  "createdAt": "2025-03-15T10:30:00Z",
  "completedAt": "2025-03-15T10:35:00Z",
  "resultUrl": "/api/v1/reports/550e8400-e29b-41d4-a716-446655440000"
}

При ошибке:

{
  "taskId": "550e8400-e29b-41d4-a716-446655440000",
  "status": "FAILED",
  "createdAt": "2025-03-15T10:30:00Z",
  "completedAt": "2025-03-15T10:32:00Z",
  "error": {
    "code": "REPORT_GENERATION_FAILED",
    "detail": "Не удалось сформировать отчет: данные за период отсутствуют"
  }
}

18.3. Статусы задачи

  • PENDING -- задача создана, ожидает обработки
  • PROCESSING -- выполняется
  • COMPLETED -- завершена, результат доступен по resultUrl
  • FAILED -- завершена с ошибкой, подробности в error

19. Локализация

19.1. Заголовок Accept-Language

Клиент указывает предпочитаемый язык для человекочитаемых сообщений (detail в ошибках, message в violations):

GET /api/v1/orders/{id}
Accept-Language: ru

19.2. Язык по умолчанию

Если Accept-Language не указан -- сервер использует язык по умолчанию, определённый для проекта (например, ru).

19.3. Что локализуется

  • detail в ProblemDetails
  • message в violations

19.4. Что НЕ локализуется

  • code -- enum, всегда на английском (ORDER_EMPTY, не ЗАКАЗ_ПУСТОЙ)
  • title -- стандартное название HTTP-статуса на английском (Bad Request, Not Found)
  • type -- URI, всегда на английском
  • Имена полей в JSON (orderId, не идЗаказа)