Генерація звіту, імпорт файлу, відео-конвертація займають хвилини. Тримати HTTP-з'єднання весь цей час не можна: спрацюють тайм-аути проксі й клієнта, а повтор запиту запустить роботу вдруге.
Шаблон «асинхронна операція»:
1. Запит створює задачу й одразу відповідає 202 Accepted:
POST /api/exports
{"type": "orders", "from": "2026-09-01"}
HTTP/1.1 202 Accepted
Location: /api/exports/7f3a
Retry-After: 5
{"data": {"id": "7f3a", "status": "queued"}}
202 означає «прийнято до обробки, але ще не виконано». Location вказує, де стежити за результатом.
2. Клієнт опитує ресурс статусу:
GET /api/exports/7f3a
{"data": {"id": "7f3a", "status": "processing", "progress": 45}}
3. Після завершення - посилання на результат:
{"data": {"id": "7f3a", "status": "completed", "result_url": "/api/exports/7f3a/download"}}
Або 303 See Other з Location на готовий ресурс. При помилці - status: "failed" з описом.
У Laravel:
public function store(StoreExportRequest $request): JsonResponse
{
$export = Export::create([...$request->validated(), 'status' => 'queued', 'user_id' => $request->user()->id]);
GenerateExport::dispatch($export);
return ExportResource::make($export)
->response()
->setStatusCode(202)
->header('Location', route('exports.show', $export));
}
Джоба оновлює status і progress моделі.
Альтернативи опитуванню:
- вебхук - сервер сам повідомляє клієнта про завершення (для інтеграцій сервер-сервер);
- WebSocket/SSE (Laravel Reverb) - для інтерфейсу користувача;
Retry-After- підказка клієнту, як часто опитувати.
Що важливо:
- ідемпотентність створення - повтор
POSTпісля обриву з'єднання не повинен ставити другу задачу (Idempotency-Key); - авторизація ресурсу статусу - лише власник бачить свою операцію;
- термін життя результату й статусу (видаляти через N днів);
- скасування -
DELETE /api/exports/7f3aчиPOST .../cancel, якщо операція довга.