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

# Idempotencia

> Cómo reintentar una solicitud sin emitir dos comprobantes.

Una emisión es una operación fiscal: si la repites por error, quedan dos comprobantes. El encabezado `Idempotency-Key` hace que repetir una solicitud sea seguro.

## Cómo funciona

* **Todos los `POST`** la exigen: emitir, anular, reintentar, notas, comprobantes especiales, registrar y probar webhooks. `PATCH` y `DELETE` no la usan: repetirlos deja el mismo resultado.
* La generas tú, una por operación: un UUID sirve (de 1 a 255 caracteres ASCII imprimibles).
* Si repites la **misma solicitud con la misma llave** en las siguientes 24 horas, recibes la respuesta original con `Idempotent-Replayed: true`, sin que se ejecute de nuevo.

| Situación | Respuesta |
| - | - |
| Primera vez | El resultado normal (por ejemplo `201`). |
| Misma llave y misma solicitud, ya terminada | La respuesta original, con `Idempotent-Replayed: true`. |
| Misma llave y misma solicitud, la primera aún en curso | `409`: espera y reintenta. |
| Misma llave con otra solicitud | `422`: usa una llave nueva para una operación distinta. |

## Qué se guarda y qué no

No se guardan los `401`, `403`, `409`, `429` ni los `5xx`, ni los `400` por errores de validación del cuerpo. Después de cualquiera de ellos puedes reintentar con la **misma** llave; por ejemplo, después de corregir el cuerpo o de esperar el `Retry-After`.

Si una emisión vence (`503`) después de llegar a AC-PAY, el reintento con la misma `Idempotency-Key` no emite otro comprobante: devuelve el ya emitido.

Antes de repetir una respuesta guardada se comprueba que tu llave de API siga vigente: con una llave revocada recibes `401`.

## Cuánto dura y qué cuenta como la misma solicitud

* **Ámbito:** la llave es de tu comercio. La misma llave en otro comercio no choca con la tuya.
* **Misma solicitud** quiere decir mismo método, misma ruta y mismo cuerpo. El cuerpo se compara como JSON con las claves ordenadas: el orden de las propiedades y los espacios no importan; los valores, sí.
* **Las 24 horas** se cuentan desde que la primera solicitud terminó. En ese plazo recibes la respuesta original tal cual, incluidos los errores `4xx` que se guardan.
* **Emisiones y registro de webhooks, 30 días:** al emitir una factura, una nota o un comprobante especial, y al registrar un webhook, AC-PAY asocia la llave al recurso creado y la conserva 30 días. Si repites la solicitud con la misma llave después de las 24 horas, pero dentro de esos 30 días, recibes el mismo recurso con `Idempotent-Replayed: true`, no uno nuevo.

### Si perdiste la respuesta

Repite la solicitud con la misma llave y el mismo cuerpo, aunque hayan pasado horas o días (hasta 30 en las emisiones): recibes el documento ya emitido, o lo emite si la primera nunca llegó. Por eso la llave tiene que vivir en tu base de datos junto al documento, no solo en memoria.

Si después de reintentar sigues recibiendo `409`, la primera solicitud no terminó de registrarse. No cambies de llave, porque podrías emitir dos veces: reporta el `X-Request-Id` a AC-PAY.

## Cómo usarla bien

1. **Genera la llave antes del primer intento y guárdala junto a tu documento** (por ejemplo, en la fila de la factura en tu base de datos).
2. **Reintenta siempre con la misma llave** cuando no sabes si la primera solicitud llegó: un timeout, un corte de red o cualquier `5xx` (`500`, `502`, `503`, `504`).
3. **Usa una llave nueva** solo para una operación nueva: otra factura, o la misma factura corregida después de un `rejected`.

```js theme={null}
// Reintento seguro: la misma llave en cada intento.
async function issueWithRetry(invoice, idempotencyKey, attempts = 3) {
  for (let attempt = 1; attempt <= attempts; attempt += 1) {
    const response = await fetch(`${BASE_URL}/invoices`, {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${API_KEY}`,
        'Idempotency-Key': idempotencyKey,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(invoice),
    });

    // 409: la primera solicitud con esta llave sigue en curso. 5xx: no se
    // sabe si llegó. En los dos casos se repite con la misma llave.
    const retryable = response.status === 409 || response.status >= 500;
    if (!retryable || attempt === attempts) {
      return response;
    }
    await new Promise((resolve) => setTimeout(resolve, attempt * 2000));
  }
}
```

Para un `429`, respeta `Retry-After` si viene. Si no trae `Retry-After`, es el límite mensual y no se resuelve reintentando: ver [Errores y límites](https://dev.acpay.do/guias/errores-y-limites.md).


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