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

# Idempotence

> Réessayer un envoi sans jamais l’envoyer deux fois.

Envoyez un en-tête **`Idempotency-Key`** (8 à 255 caractères) sur chaque envoi. Il protège d’un double envoi quand une réponse se perd — délai dépassé, coupure réseau — et que votre service réessaie.

```bash theme={null}
curl -X POST https://api.dexnotify.com/api/v1/sms/messages \
  -H "x-api-key: $DEXNOTIFY_CLE" \
  -H "Idempotency-Key: commande-48213-confirmation" \
  -H "content-type: application/json" \
  -d '{"sender":"DEXCHANGE","to":"77 123 45 67","content":"Votre commande 48213 est confirmée."}'
```

* **Même clé, même demande** : l’API rend le même message, sans rien écrire ni débiter.
* **Même clé, autre demande** (autre texte, autre numéro…) : `409 CONFLICT`.
* La clé vaut pour un projet et un environnement.

> [!TIP]
>
> Tirez la clé de votre propre identifiant métier (`commande-48213-confirmation`) plutôt que d’un aléa refait à chaque essai : c’est ce qui rend le réessai sûr.

## Un client qui réessaie bien

Un client minimal, sans dépendance (Node 18+) : il garde la même clé d’un essai à l’autre et ne réessaie que ce qui peut l’être.

```ts theme={null}
const BASE_URL = process.env.DEXNOTIFY_API_URL ?? "https://api.dexnotify.com";
const API_KEY = process.env.DEXNOTIFY_CLE!;

export class DexNotifyError extends Error {
  constructor(
    readonly status: number,
    readonly code: string,
    message: string,
    readonly requestId?: string,
  ) {
    super(message);
  }
}

export async function call<T>(
  method: "GET" | "POST" | "PATCH",
  path: string,
  body?: unknown,
  idempotencyKey?: string,
): Promise<T> {
  for (let attempt = 1; ; attempt += 1) {
    let response: Response;
    try {
      response = await fetch(`${BASE_URL}/api/v1${path}`, {
        method,
        headers: {
          "x-api-key": API_KEY,
          ...(body ? { "content-type": "application/json" } : {}),
          ...(idempotencyKey ? { "Idempotency-Key": idempotencyKey } : {}),
        },
        ...(body ? { body: JSON.stringify(body) } : {}),
        signal: AbortSignal.timeout(15_000),
      });
    } catch (error) {
      // Réponse perdue : réessayer avec la MÊME clé ne double pas l'envoi.
      if (attempt < 4) {
        await new Promise((resolve) => setTimeout(resolve, 500 * 2 ** attempt));
        continue;
      }
      throw error;
    }
    if (response.ok) return (await response.json()) as T;

    const { error } = (await response.json().catch(() => ({ error: {} }))) as {
      error: { code?: string; message?: string; requestId?: string };
    };
    const retryable = response.status === 429 || response.status >= 500;
    if (retryable && attempt < 4) {
      const after = Number(response.headers.get("retry-after")) || 2 ** attempt;
      await new Promise((resolve) => setTimeout(resolve, after * 1000));
      continue;
    }
    throw new DexNotifyError(
      response.status,
      error.code ?? "UNKNOWN",
      error.message ?? response.statusText,
      error.requestId,
    );
  }
}

// const sms = await call("POST", "/sms/messages",
//   { sender: "DEXCHANGE", to: "77 123 45 67", content: "Commande 48213 confirmée" },
//   "commande-48213-confirmation");
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.