Ідемпотентність
Заголовок Idempotency-Key: безпечні повтори запитів на запис без дублів.
Мережа ненадійна: відповідь може загубитися вже після того, як сервер обробив запит. Якщо просто
повторити POST, з'явиться дубль заявки. Щоб цього не сталося, надішліть заголовок
Idempotency-Key.
curl -X POST "https://api.samo-trans.com/api/public/v1/proposals/cargo" \
-H "Authorization: Bearer $SAMO_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-88213-attempt-1" \
-d '{ "startLocations": [ … ], "endLocations": [ … ], … }'
Заголовок необов'язковий і діє лише на запити, що змінюють дані (POST, PATCH, DELETE).
На GET він ігнорується — читання й так безпечно повторювати.
Як обирати ключ
Ключ має бути стабільним для однієї логічної операції та різним для різних. Найнадійніше — взяти ідентифікатор із вашої системи:
Idempotency-Key: tms-order-88213
Не використовуйте випадковий рядок, згенерований під час повтору: тоді повтор виглядатиме як нова операція, і сенс заголовка зникає. Генеруйте ключ один раз, коли вирішили виконати операцію, і використовуйте його для всіх спроб.
Поведінка
| Ситуація | Результат |
|---|---|
| Перший запит із ключем | Виконується звичайним чином, відповідь запам'ятовується на 24 години |
| Повтор: той самий ключ, те саме тіло | Повертається збережена відповідь. Друга заявка не створюється |
| Повтор: той самий ключ, інше тіло | 422 з кодом validation_error — ключ уже використано для іншої операції |
| Повтор, поки перший запит ще виконується | 409 з кодом conflict — спробуйте ще раз за мить |
Вікно захисту від паралельного дубля — 60 секунд. Якщо перший запит завершився раніше, наступний одразу отримає збережену відповідь.
Ключ діє в межах одного ключа API, методу й шляху: POST /proposals/cargo і
POST /proposals/transport з однаковим Idempotency-Key — це дві різні операції.
Рекомендований цикл повтору
async function createWithRetry(payload, idempotencyKey) {
for (let attempt = 0; attempt < 5; attempt += 1) {
const res = await fetch(`${API_BASE}/api/public/v1/proposals/cargo`, {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.SAMO_KEY}`,
"Content-Type": "application/json",
// той самий ключ на всіх спробах — у цьому вся суть
"Idempotency-Key": idempotencyKey,
},
body: JSON.stringify(payload),
});
if (res.ok) return res.json();
const { error } = await res.json();
// 409 — попередня спроба ще в польоті; 429 — ліміт. Обидва варті повтору.
if (error.code === "conflict" || error.code === "rate_limited") {
const retryAfter = Number(res.headers.get("Retry-After") ?? 0);
await sleep(retryAfter * 1000 || 2 ** attempt * 500);
continue;
}
throw new Error(`${error.code}: ${error.message} (requestId ${error.requestId})`);
}
throw new Error("giving up after 5 attempts");
}
Разом із пакетним створенням
POST /proposals/batch теж підтримує Idempotency-Key — і саме там він найкорисніший:
повторна спроба після обриву зв'язку не створить 25 дублів, а поверне збережений результат
першої спроби разом із поточним станом кожної позиції.