Перейти к содержимому

Идемпотентность

Стандартный production-паттерн (Stripe, OpenAI, etc): на любой POST-запрос клиент может прислать Idempotency-Key: <unique-id>. Если запрос повторяется с тем же ключом в течение 24 часов — сервер возвращает закэшированный ответ, без создания второй задачи и без второго списания.

Зачем это нужно:

  • Сетевые ретраи. Запрос дошёл, ответ потерялся — клиент думает что упало, ретраит. Без идемпотентности — две задачи, два списания.
  • Двойной клик в UI — кнопка «Сгенерировать» нажата дважды.
  • Общая надёжность — гарантия что «один логический запрос = одна задача».

Как использовать

Сгенерируй уникальный ID (UUID v4 — обычный выбор) и пришли в заголовке:

POST /generate
Authorization: Bearer <NEXUS_KEY>
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
Content-Type: application/json
{"params": {"model_name": "veo-3-fast", "prompt": "...", "duration": 8}}

Через любой промежуток времени до 24 часов повторение того же запроса с тем же Idempotency-Key вернёт тот же task_id — никаких дублей.

В SDK

import uuid
from 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, # ← вот так
)

Поддерживаемые эндпоинты

  • POST /generate — native API
  • POST /v1/images/generations — OpenAI-совместимый (sync). Кэшируется финальный ответ с URL — повторный запрос вернёт тот же URL без re-generation.
  • POST /v1/videos — OpenAI-совместимый (Sora-style)

Остальные эндпоинты не требуют идемпотентности — они либо идемпотентны сами по себе (GET), либо безопасно повторяются (DELETE).

Правила сервера

  1. Тот же ключ + то же тело → возвращаем закэшированный ответ. Никакого создания задачи, никакого списания.
  2. Тот же ключ + другое тело → 422 Unprocessable Entity с сообщением “Idempotency-Key was reused with a different request body”. Это почти всегда баг на клиенте.
  3. Ключ не передан → обычный flow, никакой защиты.
  4. 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 — он и так идемпотентен.