---
title: Idempotency & rate limit
description: Rendi le scritture ripetibili con Idempotency-Key, gestisci il rate limit per chiave e conosci i limiti operativi dell'API.
---

Due meccanismi rendono robuste le integrazioni server-to-server: l'header
`Idempotency-Key` per evitare scritture duplicate sui retry, e il **rate limit**
per chiave con header informativi per regolare il traffico.

## Idempotency

Le rotte **`POST`** supportano l'header `Idempotency-Key`. Serve a garantire che
un retry della stessa richiesta (per timeout, errore di rete, ecc.) **non crei
duplicati**: Audin riconosce la chiave e ritorna la risposta originale.

- **Scope**: solo rotte `POST`.
- **TTL**: la risposta `2xx` viene conservata per **24 ore** associata alla
  chiave.
- **Errori non cachati**: se la prima chiamata fallisce (status **non-2xx**), la
  chiave **non** viene persistita — puoi ritentare liberamente.
- **Body mismatch**: se riusi la stessa chiave con un **body diverso**, ricevi
  **`422 IDEMPOTENCY_KEY_MISMATCH`**.
- **Lunghezza chiave**: massimo **255 caratteri**. È consigliato un **UUID v4**.

### Come funziona

<Steps>
<Step>
Genera un **UUID v4** e invialo nell'header `Idempotency-Key` su una richiesta
`POST`.
</Step>

<Step>
Audin salva la risposta `2xx` per **24 ore**, associata a quella chiave.
</Step>

<Step>
Se la **stessa chiave** arriva di nuovo con lo **stesso body**, Audin ritorna la
risposta cachata invece di creare un nuovo record.
</Step>

<Step>
Se la stessa chiave arriva con un **body diverso**, ricevi
**`422 IDEMPOTENCY_KEY_MISMATCH`**: usa una chiave fresca per la nuova operazione.
</Step>

<Step>
Se la prima chiamata era fallita (non-2xx), la chiave non è stata persistita:
puoi semplicemente ritentare.
</Step>
</Steps>

### Esempio

<Tabs items={["curl", "JavaScript"]}>
<Tab value="curl">

```bash
IDEM=$(uuidgen)

# Prima chiamata — crea il lead
curl -X POST https://api.audin.ai/leads \
  -H "X-API-Key: $API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $IDEM" \
  -d '{"fullName":"Mario Rossi","phoneNumber":"+393331234567"}'

# Retry identico — ritorna lo stesso lead, NON crea un duplicato
curl -X POST https://api.audin.ai/leads \
  -H "X-API-Key: $API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $IDEM" \
  -d '{"fullName":"Mario Rossi","phoneNumber":"+393331234567"}'
```

</Tab>
<Tab value="JavaScript">

```js
import { randomUUID } from "node:crypto";

const idempotencyKey = randomUUID(); // UUID v4
const body = JSON.stringify({
  fullName: "Mario Rossi",
  phoneNumber: "+393331234567",
});

async function createLead() {
  return fetch("https://api.audin.ai/leads", {
    method: "POST",
    headers: {
      "X-API-Key": process.env.AUDIN_API_KEY,
      "Content-Type": "application/json",
      // Riusa la STESSA chiave per i retry della stessa operazione.
      "Idempotency-Key": idempotencyKey,
    },
    body, // stesso body a ogni retry: un body diverso → 422
  });
}

// Un retry con la stessa chiave + stesso body ritorna il lead originale.
await createLead();
await createLead();
```

</Tab>
</Tabs>

<Callout type="info">
  **Best practice.** Usa un UUID v4 fresco per ogni operazione logicamente
  distinta; riusa la **stessa** chiave **solo** per ritentare quella precisa
  chiamata (timeout, errore di rete). Non condividere chiavi tra operazioni
  diverse.
</Callout>

## Rate limit

Ogni API Key ha un limite di **100 richieste al minuto** (sliding window).

Ogni risposta include header informativi che ti permettono di regolare il
traffico **prima** di incappare nel limite:

| Header | Significato |
|--------|-------------|
| `X-RateLimit-Limit` | limite totale della finestra (`100`) |
| `X-RateLimit-Remaining` | richieste residue nella finestra corrente |
| `X-RateLimit-Reset` | unix timestamp dello sblocco della finestra |
| `Retry-After` | secondi da attendere (presente **solo** sulle risposte `429`) |

Quando superi il limite ricevi `429` con un `code` dedicato:

```json
{
  "success": false,
  "error": {
    "code": "RATE_LIMITED",
    "message": "Too many requests, please try again later."
  }
}
```

<Callout type="info">
  Questo envelope con `code`/`message` è la forma usata dai controlli
  **trasversali** — il rate limiter (`RATE_LIMITED`, `429`) e il mismatch di
  idempotency (`IDEMPOTENCY_KEY_MISMATCH`, `422`) — e differisce dal formato
  **raw** `{ "error": "..." }` delle rotte di risorsa (vedi
  [Formato e convenzioni](/docs/api-rest/formato-e-convenzioni)). Distingui i
  due casi sul `code` quando gestisci gli errori a livello di codice.
</Callout>

<Callout type="warn">
  Su `429`, **rispetta sempre l'header `Retry-After`**: attendi i secondi
  indicati prima di ritentare e implementa un **backoff esponenziale** (con
  jitter) per le richieste successive. Non ritentare in loop stretto.
</Callout>

## Limiti operativi

| Parametro | Limite |
|-----------|--------|
| Rate limit | **100 richieste / minuto per API Key** |
| Dimensione massima del body | **10 MB** |
| `pageSize` (query param) | **max 100** |
| Webhook / push notification | **Non supportati** — modello polling-only |
| Lunghezza massima `Idempotency-Key` | **255 caratteri** (consigliato UUID v4) |

<Callout type="info">
  Non esistono webhook: gli stati che evolvono nel tempo (es. la firma di un
  contratto) si seguono con **polling** periodico della risorsa. Vedi
  [Flussi operativi](/docs/api-rest/flussi).
</Callout>
