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

Как повторить запрос после таймаута или обрыва связи и не продать один билет дважды.

Связь рвётся. Запрос мог дойти и выполниться, а ответ потеряться. Чтобы повтор не создал второй заказ, передайте заголовок `Idempotency-Key`:

```http
Idempotency-Key: order-1042
```

## Правила {#rules}

- Ключ учитывается в запросах `POST` и `DELETE`. В `GET`, `PATCH` и `PUT` он не нужен.
- Ключ — любая строка до 255 символов. Удобно брать номер заказа на вашей стороне.
- Ключ живёт 24 часа с первого запроса. Ключи разных организаций и режимов не пересекаются: тестовый запрос никогда не получит ответ боевого.
- Повтор с тем же ключом, методом, путём и телом получает сохранённый ответ: тот же статус и то же тело байт в байт, с заголовком `Idempotent-Replayed: true`.
- Тело сравнивается без пробелов между элементами JSON, но порядок полей и запись чисел важны. `{"a":1,"b":2}` и `{"b":2,"a":1}` — разные запросы.
- Тот же ключ с другим телом или путём получит `409 idempotency_conflict`.

## Где ключ обязателен {#required}

[`POST /v1/events/{id}/issue`](https://budu.events/docs/issue/#issue) без ключа отвечает `400 invalid_request`. Для заказа, резерва и события ключ не обязателен, но без него повтор создаст новый объект. Передавайте ключ во всех запросах, которые что-то создают.

## Что вернёт повтор {#outcomes}

| Первый запрос | Повтор с тем же ключом |
|---|---|
| ответил `2xx` | тот же ответ |
| ответил `4xx`, например `409 sold_out` | тот же отказ, даже если билеты потом вернулись в продажу. Для новой попытки нужен новый ключ |
| ещё выполняется | `409 idempotency_conflict`: повторите через несколько секунд |
| упал с `5xx` до того, как мы начали создавать объект | выполняется заново |
| упал с `5xx` во время создания заказа, события или выдачи | `409 idempotency_conflict` с `details.idempotency_state: "unknown"`, см. ниже |
| подтверждение резерва ответило `202` | выполняется заново и показывает свежее состояние заказа |

Отмена резерва, отмена билета, перевыпуск и публикация безопасны сами по себе: их повтор после сбоя на нашей стороне просто выполнится ещё раз.

## Неизвестный результат {#unknown}

Если создание заказа оборвалось на нашей стороне, заказ мог успеть создаться. Повтор с тем же ключом ответит так:

```json Ответ 409
{
  "error": {
    "code": "idempotency_conflict",
    "message": "Результат запроса с этим Idempotency-Key неизвестен: он мог выполниться. Проверьте состояние, прежде чем повторять запрос с новым ключом",
    "details": {
      "idempotency_state": "unknown",
      "order_id": "0193e0c5-1f20-7c3e-b6a1-9d4e2f7a8b10"
    }
  }
}
```

Прочитайте заказ из `details.order_id` через [`GET /v1/orders/{id}`](https://budu.events/docs/orders/#get). Заказ в статусе `paid` или `issued` уже продан, второй не нужен. Новый заказ с новым ключом создавайте, только если заказа нет или он `cancelled`.

## Где ключ не действует {#ignored}

- `POST /v1/devices/invites`: каждый вызов создаёт новое приглашение.
- `POST /v1/events/{id}/seats`: загрузка мест заменяет все места черновика, повтор того же файла даёт тот же результат.
- `POST /v1/events/{id}/imports`: тот же список гостей для того же события возвращает прежнюю загрузку с `repeated_file: true`, второй раз билеты не выдаются.
- `PUT /v1/events/{id}/cover`: повтор ставит ту же обложку.
