# Cereri repetate fără dubluri

Cum trimiți din nou o scriere după o eroare de rețea fără să creezi același document de două ori: antetul Idempotency-Key.

## La ce folosește

Când o cerere care creează ceva (o ofertă, un document, o tranzacție) rămâne fără răspuns din cauza rețelei, nu știi dacă a ajuns. Dacă o trimiți din nou, poți crea același lucru de două ori. Cu antetul `Idempotency-Key`, a doua trimitere cu aceeași cheie nu mai rulează operația: primești răspunsul salvat de la prima.

Antetul urmează propunerea IETF [draft-ietf-httpapi-idempotency-key-header-08](https://datatracker.ietf.org/doc/draft-ietf-httpapi-idempotency-key-header/). Este opțional: fără el, ruta merge exact ca înainte.

## Rutele care îl acceptă

Antetul contează doar pe rutele de mai jos (7). Pe celelalte este ignorat. În referința API, aceste rute au mențiunea „acceptă `Idempotency-Key`”.

| Metodă | Cale |
| --- | --- |
| `POST` | [`/v1/evidenta/cereri`](#api-evidenta--post-v1-evidenta-cereri) |
| `POST` | [`/v1/evidenta/recurente`](#api-evidenta--post-v1-evidenta-recurente) |
| `POST` | [`/v1/evidenta/tranzactii`](#api-evidenta--post-v1-evidenta-tranzactii) |
| `POST` | [`/v1/oferte`](#api-oferte--post-v1-oferte) |
| `POST` | [`/v1/oferte/{id}/duplica`](#api-oferte--post-v1-oferte-id-duplica) |
| `POST` | [`/v1/registru/numar`](#api-registru--post-v1-registru-numar) |
| `POST` | [`/v1/registru/oferte`](#api-registru--post-v1-registru-oferte) |

## Cum îl folosești

### Alege o cheie nouă pentru fiecare operație

Cheia are între 1 și 255 de caractere ASCII vizibile, fără spațiu, ghilimele (`"`) sau bară inversă (`\`). Un UUID nou pentru fiecare operație este alegerea simplă. O poți trimite simplă (`abc`) sau între ghilimele (`"abc"`), cum cere propunerea IETF.

### Trimite cererea cu antetul

```bash title="Prima trimitere"
curl -X POST https://app.ilustromania.com/nucleu/v1/oferte \
  -H "Cookie: __Host-sesiune=$JETON_SESIUNE" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 8f14e45f-ea8e-4c2b-9b1a-2f1c0d3e4a5b" \
  -d '{ … }'
```

### La o eroare de rețea, trimite din nou aceeași cerere

Aceeași cheie, același corp, aceeași adresă. Dacă prima cerere s-a terminat, primești același cod și același corp, cu antetul `Idempotency-Replayed: true`. Operația nu rulează a doua oară.

```http title="Răspunsul la repetare"
HTTP/1.1 201 Created
Idempotency-Replayed: true
Cache-Control: no-store
Content-Type: application/json; charset=utf-8

{ … același corp ca la prima trimitere … }
```

## Răspunsurile posibile

| Situație | Răspuns |
| --- | --- |
| Cheie nouă | ruta rulează normal; răspunsul se salvează |
| Aceeași cheie, același corp, prima cerere terminată | răspunsul salvat, cu `Idempotency-Replayed: true` |
| Aceeași cheie, alt corp | `422` cu `{"eroare": "idempotency_key_alt_corp"}` |
| Aceeași cheie, prima cerere încă rulează | `409` cu `{"eroare": "idempotency_key_in_lucru"}`; încerci din nou puțin mai târziu |
| Antet cu caractere nepermise, gol sau mai lung de 255 | `400` cu `{"eroare": "idempotency_key_invalid"}` |
| Fără sesiune sau fără acces la firmă | antetul nu se citește; ruta răspunde singură `401` sau `403` |

Cu `Accept: application/problem+json`, aceste erori vin în formatul RFC 9457, cu același `cod` (vezi [Erori](#api-erori)).

## Ce se păstrează și cât timp

- O cheie se ține **24 de ore**. După aceea, aceeași cheie este din nou o cheie nouă.
- Se salvează răspunsurile JSON (sau fără corp) cu cod `2xx` și erorile `4xx` care nu depind de moment.
- **Nu se salvează** `5xx` și nici `401`, `403`, `408`, `409`, `425`, `429`: cheia se eliberează, iar repetarea rulează din nou operația.
- Repetările trec și ele prin [limita generală](#limite): fiecare trimitere se numără.

> [!WARNING]
> Corpul se compară exact, după JSON-ul primit, inclusiv ordinea câmpurilor. La repetare trimite același text, nu un obiect construit din nou cu câmpurile în altă ordine, altfel primești `422`.

**Cum se formează cheia pe server**
Cheia se ține separat pentru fiecare combinație de:

- **firmă**: firma activă a sesiunii; pe ruta publică a comenzilor de catering, firma restaurantului;
- **rută**: metoda, șablonul rutei și adresa exactă, cu parametri și interogare (`/v1/oferte/A/duplica` și `/v1/oferte/B/duplica` sunt chei diferite);
- **cine trimite**: contul din sesiune, împreună cu rolul și funcțiile lui; pe ruta publică, adresa IP;
- **valoarea antetului**.

Două conturi care folosesc aceeași valoare nu se încurcă. Dacă rolul sau funcțiile contului se schimbă între două trimiteri, a doua este tratată ca o cheie nouă, iar ruta verifică din nou dreptul. Cheile stau în tabelul `chei_idempotenta`, izolat pe firmă, ca orice alt tabel al firmei.

## Când prima cerere s-a oprit la mijloc

Dacă procesul s-a oprit în timpul primei cereri sau răspunsul ei nu s-a putut salva, cheia rămâne „în lucru”: orice repetare primește `409` până expiră, după 24 de ore. Platforma nu o reia singură, ca operația să nu ruleze de două ori.

> [!TIP]
> Dacă primești `409` mult timp, verifică întâi dacă obiectul s-a creat (de exemplu în lista ofertelor). Dacă nu există, trimite cererea cu o cheie nouă.
