Idempotencia
Una red falla en el peor momento: enviaste la factura y la respuesta no llegó. ¿Se emitió?
Si reintentas a ciegas, puedes emitir dos. La cabecera Idempotency-Key lo resuelve: la
primera petición con una llave crea el recurso, y repetirla devuelve ese mismo recurso.
curl https://api.emitay.com/v1/invoices \ -H "Authorization: Bearer $EMITAY_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: factura-pedido-8412" \ -d '{ "series": "F001", "customer": { "document_number": "20100066603", "name": "CLIENTE S.A." }, "items": [ { "description": "Servicio de consultoría", "quantity": 2, "unit_price": "50.00" } ], "metadata": { "pedido": "8412" } }'$cabeceras = @{ Authorization = "Bearer $env:EMITAY_API_KEY" "Idempotency-Key" = "factura-pedido-8412"}$cuerpo = @'{ "series": "F001", "customer": { "document_number": "20100066603", "name": "CLIENTE S.A." }, "items": [ { "description": "Servicio de consultoría", "quantity": 2, "unit_price": "50.00" } ], "metadata": { "pedido": "8412" }}'@Invoke-RestMethod -Method Post ` -Uri https://api.emitay.com/v1/invoices ` -Headers $cabeceras ` -ContentType "application/json; charset=utf-8" ` -Body $cuerpoimport { Emitay } from "emitay";
const emitay = new Emitay(); // toma la llave de EMITAY_API_KEY
const factura = await emitay.invoices.create({ series: "F001", customer: { document_number: "20100066603", name: "CLIENTE S.A." }, items: [ { description: "Servicio de consultoría", quantity: 2, unit_price: "50.00" }, ], metadata: { pedido: "8412" },}, { idempotencyKey: "factura-pedido-8412" });- La primera vez responde
201. Las siguientes,200con el mismo comprobante como está ahora (ya con el veredicto de SUNAT, si llegó). - No se compara el cuerpo: la llave identifica la operación. Usa un valor que ya tengas y sea único para lo que emites: el id del pedido, de la venta, del cobro.
- Una petición rechazada (
422) no registra la llave: corrige y reintenta con la misma.
Una llave por comprobante
Sección titulada «Una llave por comprobante»La llave vale por un recurso. Si usas la del pedido para su factura, usa otra para su nota
de crédito (nota-pedido-8412). La misma llave en una petición de otro tipo responde
409 conflict nombrando el comprobante que ya creó, en lugar de
devolvértelo como si hubieras emitido la nota.
Dónde se acepta
Sección titulada «Dónde se acepta»En todo POST que crea algo: emitir cualquier comprobante, anular,
enviar por correo, crear un endpoint de webhook y crear un layout.
La llave de un comprobante no caduca. La de los demás recursos se recuerda 30 días.
Con el SDK no hay nada que recordar
Sección titulada «Con el SDK no hay nada que recordar»Toda llamada del SDK que crea algo lleva una Idempotency-Key. Si no das una, genera la
suya y la repite en cada reintento automático: un corte de red en mitad de una emisión no
duplica la factura.
Da la tuya cuando la llamada misma se puede repetir: un trabajo que corre dos veces, un proceso que se reinicia, un usuario que pulsa otra vez.
| Llave generada (por defecto) | Llave propia (idempotencyKey) |
|
|---|---|---|
| Reintentos del SDK ante un fallo de red | No duplican | No duplican |
| Volver a ejecutar tu código para el mismo pedido | Emite otro comprobante | Devuelve el mismo |
Qué reintentar
Sección titulada «Qué reintentar»| Respuesta | ¿Reintentar? |
|---|---|
Sin respuesta, 5xx |
Sí, con la misma Idempotency-Key |
429 |
Sí, tras los segundos de Retry-After |
4xx (salvo 429) |
No: la petición fallará igual hasta que la corrijas |
El SDK hace exactamente esto: reintenta dos veces, con una pausa que crece, solo lo que se puede repetir sin hacerlo dos veces.
import { Emitay } from "emitay";
const emitay = new Emitay(process.env.EMITAY_API_KEY, { maxRetries: 2, // reintentos de cada llamada; 0 los desactiva timeoutMs: 60_000, // cuánto espera una respuesta});