# Idempotence

> Le comportement exact de l'Idempotency-Key : format, portée, fenêtre de 24 heures, ce qui est comparé, ce qui est mémorisé, et ce qui se passe en cas de reprise, de conflit ou de concurrence.

L'en-tête `Idempotency-Key` rend une requête rejouable sans danger : si la réponse se perd sur le réseau, la même requête renvoyée avec la même clé rend la réponse déjà calculée au lieu de calculer et de facturer une seconde fois.

## Format

- 1 à 255 octets, ASCII visible uniquement (`0x21` à `0x7E`) : pas d'espace, pas de tabulation, pas de caractère accentué.
- Aucune normalisation : la valeur envoyée est la valeur stockée, comparée octet pour octet.
- Un en-tête vide, trop long, contenant un caractère interdit ou répété est refusé en `400 invalid_idempotency_key`, avant tout calcul.

Un UUID par opération logique est un bon choix. Générez-le **une fois**, avant le premier essai, et gardez-le pour toutes les reprises de cette opération.

## Portée et fenêtre

| Propriété | Valeur |
|---|---|
| Portée | Le workspace et l'environnement de la clé API. Toutes les clés API d'un même workspace partagent cet espace ; Sandbox et Production sont séparés. |
| Fenêtre | 24 heures à partir de la première requête. |
| Après 24 heures | La clé redevient libre, y compris pour une requête différente. |

## Ce qui est comparé

Deux requêtes sont « la même requête » quand elles portent sur la même opération (création ou rafraîchissement d'une cible donnée), la même entrée économique sous sa forme canonique et le même `external_transaction_id`. L'ordre des champs JSON, la casse et les espaces du `descriptor`, ou une fraction de seconde dans `occurred_at` ne changent rien. Un montant, une devise ou un identifiant différents font une autre requête.

Une même clé utilisée pour une création puis pour un rafraîchissement désigne deux requêtes différentes : la seconde rend `409 idempotency_key_reuse`.

## Ce qui se passe

| Situation | Réponse |
|---|---|
| Première requête avec cette clé | Traitée normalement |
| Même clé, même requête, après un `200` | Le corps mémorisé de la première réponse, `Noryo-Idempotency-Replayed: true`. Rien n'est recalculé ni facturé. |
| Même clé, requête différente, dans les 24 heures | `409 idempotency_key_reuse` |
| Même clé, même requête, pendant que la première est encore en cours | La seconde attend la fin de la première, puis rend sa réponse en rejeu ; si l'attente dure trop, `503 busy` avec `Retry-After` |
| Même clé, même requête, après un refus rendu au règlement (`402`, `403 production_not_enabled`, `409 transaction_id_conflict`, `410`, `503`) | Réévaluée comme une nouvelle tentative : un refus n'est pas mémorisé |
| Même clé, requête différente, après un tel refus | `409 idempotency_key_reuse` : un refus ne libère pas la clé |
| Même clé après un refus antérieur au règlement (`400` de validation, `401`, `403 insufficient_scope`, `429`) | La clé n'avait pas été réservée : elle reste libre |
| Même clé après 24 heures | Traitée comme une première requête |

Deux règles résument le tableau. **Seul un succès est mémorisé** : une erreur se rejoue en recalculant, ce qui permet de reprendre après une panne. **Le lien entre une clé et sa requête ne change jamais pendant 24 heures**, même après une erreur : une clé ne se recycle pas pour une autre charge.

## Ce que contient un rejeu

- Le **même corps**, `usage` compris : une réponse facturée à l'origine affiche encore `billable: true`. Ne la comptez pas une seconde fois.
- L'en-tête `Noryo-Idempotency-Replayed: true`.
- Un **nouveau** `Noryo-Request-Id` : chaque tentative a son identifiant, rejeu compris.
- Un jeton de débit consommé, comme toute requête authentifiée.

## Requêtes concurrentes

Deux requêtes identiques envoyées en même temps avec la même clé ne produisent jamais deux règlements. La seconde attend la première au lieu d'être refusée : elle reçoit ensuite le rejeu de la réponse. Si l'attente dépasse le délai que le serveur s'accorde, elle reçoit `503 busy` avec un `Retry-After` : rien n'a été écrit ni facturé, et la même requête peut être renvoyée telle quelle.

```json
{
  "type": "https://developer.noryo.app/errors/busy",
  "title": "Service busy",
  "status": 503,
  "code": "busy",
  "detail": "The service is temporarily contended. Retry after the delay given by Retry-After.",
  "request_id": "req_…"
}
```

## Choisir ses clés

- Une clé par opération logique, jamais une clé par session ou par client.
- Plusieurs services dans un même workspace partagent l'espace des clés : préfixez-les, par exemple `facturation-<uuid>`.
- Une reprise réutilise la même clé ; une nouvelle opération en prend une nouvelle.
- Ne mettez dans la clé aucune donnée personnelle : elle n'est pas journalisée en clair, mais rien ne justifie d'y placer une information.

---

Page canonique : https://developer.noryo.app/fr-FR/merchant/requests/idempotency
