Идемпотентность
Стандартный production-паттерн (Stripe, OpenAI, etc): на любой POST-запрос
клиент может прислать Idempotency-Key: <unique-id>. Если запрос повторяется
с тем же ключом в течение 24 часов — сервер возвращает закэшированный ответ,
без создания второй задачи и без второго списания.
Зачем это нужно:
- Сетевые ретраи. Запрос дошёл, ответ потерялся — клиент думает что упало, ретраит. Без идемпотентности — две задачи, два списания.
- Двойной клик в UI — кнопка «Сгенерировать» нажата дважды.
- Общая надёжность — гарантия что «один логический запрос = одна задача».
Как использовать
Сгенерируй уникальный ID (UUID v4 — обычный выбор) и пришли в заголовке:
POST /generateAuthorization: Bearer <NEXUS_KEY>Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000Content-Type: application/json
{"params": {"model_name": "veo-3-fast", "prompt": "...", "duration": 8}}Через любой промежуток времени до 24 часов повторение того же запроса с тем
же Idempotency-Key вернёт тот же task_id — никаких дублей.
В SDK
import uuidfrom nexusapi import NexusClient
client = NexusClient(api_key="...")key = str(uuid.uuid4()) # сохрани в БД на случай retry
task = client.videos.create( model="veo-3-fast", prompt="A cat", duration=8, idempotency_key=key, # ← вот так)import { NexusClient } from "nexusapi-sdk";import { randomUUID } from "node:crypto";
const client = new NexusClient({ apiKey: process.env.NEXUS_KEY! });const key = randomUUID(); // сохрани в БД на случай retry
const task = await client.videos.create({ model: "veo-3-fast", prompt: "A cat", duration: 8, idempotencyKey: key,});KEY=$(uuidgen)curl -X POST https://nexusapi.dev/generate \ -H "Authorization: Bearer $NEXUS_KEY" \ -H "Idempotency-Key: $KEY" \ -H "Content-Type: application/json" \ -d '{"params": {"model_name": "veo-3-fast", "prompt": "...", "duration": 8}}'Поддерживаемые эндпоинты
POST /generate— native APIPOST /v1/images/generations— OpenAI-совместимый (sync). Кэшируется финальный ответ с URL — повторный запрос вернёт тот же URL без re-generation.POST /v1/videos— OpenAI-совместимый (Sora-style)
Остальные эндпоинты не требуют идемпотентности — они либо идемпотентны сами по себе (GET), либо безопасно повторяются (DELETE).
Правила сервера
- Тот же ключ + то же тело → возвращаем закэшированный ответ. Никакого создания задачи, никакого списания.
- Тот же ключ + другое тело →
422 Unprocessable Entityс сообщением “Idempotency-Key was reused with a different request body”. Это почти всегда баг на клиенте. - Ключ не передан → обычный flow, никакой защиты.
- TTL 24 часа. После этого ключ забывается.
Какие ответы кэшируются
- ✅ 2xx (
202 Acceptedот/generate,200 OKот/v1/*) - ✅ 4xx (например,
402 Payment Required— баланса не хватало; ретрай даже после пополнения баланса вернёт ту же 402, нужен новый Idempotency-Key) - ❌ 5xx — НЕ кэшим. Сервер сломался — клиент должен иметь возможность повторить с тем же ключом и получить нормальный ответ.
Формат ключа
- 8-256 символов. Короче —
400 Bad Request. - Любые ASCII-символы. UUID v4 (36 символов) — стандартный выбор, но можно
использовать любые осмысленные ID — например,
order_12345_retry_1. - Per-API-key namespace. Клиенты с разными API-ключами не могут случайно
конфликтнуть — ключи
aaa-bbb-cccпод разными API-ключами это два разных кэша.
Что НЕ делает Idempotency-Key
- Не возвращает деньги. Кэшируется ОТВЕТ, не статус задачи. Если первая
задача создалась успешно (202) и потом упала с
status=failed(рефанд уже выполнен) — повторный запрос с тем же ключом вернёт тот жеtask_id(уже с возвращёнными деньгами). Никакой новой задачи не создаст. - Не задерживает запрос. Если в этот момент идёт первый запрос с тем же ключом и ещё не успел сохранить ответ — второй запрос создаст новую задачу (race window короткий, но существует). Для критичных кейсов клиенту стоит серсериализовать ретраи самостоятельно.
- Не делает POST идемпотентным сам по себе. Бессмысленно посылать
Idempotency-Keyна GET — он и так идемпотентен.