> ## Documentation Index
> Fetch the complete documentation index at: https://docs.retriever.run/llms.txt
> Use this file to discover all available pages before exploring further.

# Rendre vos intégrations robustes

> Idempotence, retries, polling économe et reprise après crash pour une intégration fiable de l'API Retriever.

Les exemples Node utilisent [le client Node commun](/recipes/client) (`retriever.mjs`).

**Problème** : timeouts réseau, `429`, redémarrages. Vous voulez zéro doublon et zéro run perdu.

### Idempotence de la création

Générez l'`Idempotency-Key` **avant** l'appel et stockez-la avec votre job. Si l'appel échoue sans réponse, rejouez-le avec la même clé : vous récupérez le même run.

```js theme={null}
import { randomUUID } from "node:crypto";
import { call } from "./retriever.mjs";

job.idempotencyKey ??= randomUUID();
await db.jobs.save(job); // persister AVANT l'appel
const run = await call("/v1/runs", { method: "POST", headers: { "Idempotency-Key": job.idempotencyKey }, body: job.payload });
job.runId = run.id;
await db.jobs.save(job);
```

`409 idempotency_conflict` signifie qu'une clé a été réutilisée avec un autre corps. C'est un bug du client : ne réessayez pas.

`POST /v1/workspaces`, `POST /v1/workspaces/{id}/tables`, `POST /v1/go/*` et `POST /services/{name}` n'ont pas d'idempotence. Ne les rejouez pas automatiquement après un `5xx` ou un timeout : vérifiez d'abord si la ressource existe.

### Réessayer seulement ce qui peut l'être

| Statut                                                        | Réessayer ?                                                                           |
| ------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
| `429`                                                         | Oui, après `Retry-After`.                                                             |
| `500`, `502`, `503`, erreur réseau                            | Oui pour `GET` et `POST /v1/runs` avec `Idempotency-Key`. Non pour les autres `POST`. |
| `409 conversation_busy`                                       | Oui, après la fin du run actif de la conversation.                                    |
| `400`, `401`, `402`, `404`, `409 idempotency_conflict`, `415` | Non. Corrigez la requête ou le compte.                                                |

Le client `call()` applique ces règles, sauf `conversation_busy`, que vous gérez selon votre logique métier.

### Polling économe

* Respectez `Retry-After` (15 s pour un run actif). Poller plus vite consomme votre budget `reads` sans accélérer le run.
* Envoyez `If-None-Match` avec l'`ETag` précédent : le `304` n'a pas de corps. Il compte quand même dans le budget `reads`.

```js theme={null}
import { API, KEY, sleep } from "./retriever.mjs";

async function pollWithEtag(id) {
  let etag;
  let run;
  for (;;) {
    const res = await fetch(`${API}/v1/runs/${id}`, {
      headers: { Authorization: `Bearer ${KEY}`, ...(etag ? { "If-None-Match": etag } : {}) },
    });
    if (res.status === 200) {
      etag = res.headers.get("etag");
      run = await res.json();
      if (!["queued", "running", "recovering"].includes(run.status)) return run;
    } else if (res.status !== 304 && res.status !== 429 && res.status < 500) {
      throw new Error(`poll ${id}: HTTP ${res.status}`);
    }
    await sleep(Number(res.headers.get("retry-after") || 15) * 1000);
  }
}
```

### Parallélisme

Par défaut, une clé peut créer 30 runs par minute. La limite « 3 simultanées » porte sur les requêtes HTTP en cours, pas sur les runs actifs. Pour un gros batch, étalez les créations (par exemple une toutes les 2 s) plutôt qu'un `Promise.all` sur 100 éléments. Pour `POST /v1/go/run` (synchrone), limitez-vous à 3 appels en parallèle par clé.

### Reprise après crash

Stockez `run.id` dès la réponse `202`. Au redémarrage, reprenez le polling des runs non terminés au lieu d'en créer de nouveaux. Un run en `recovering` reprend tout seul côté Retriever.
