Idempotencia (Idempotency-Key)
Cómo usar el encabezado Idempotency-Key para que los reintentos de tus integraciones no dupliquen órdenes de compra ni apliquen una recepción dos veces.
Si integras Mosce ERP con un sistema externo (un ERP de terceros, el servicio contable de tu negocio o tus propios scripts), el encabezado
Idempotency-Keyte garantiza que un reintento por timeout de red, un redespliegue o un doble envío no cree dos veces la misma operación. El servidor reconoce que ya procesó esa operación y devuelve la respuesta original.
Tiempo de lectura: ~6 min
¿Usas Mosce ERP solo desde la interfaz web? Entonces no tienes que hacer nada: la aplicación genera y envía la llave automáticamente. Este artículo es para integradores que llaman a la API directamente.
Qué es y por qué importa
Algunas operaciones de la API mutan datos (crean una orden de compra, la envían, la reciben, la cancelan). Si tu integración envía la petición y la red se corta antes de recibir la respuesta, no sabes si la operación se completó. Reintentar a ciegas podría crear una segunda orden o aplicar una recepción dos veces.
El encabezado Idempotency-Key resuelve esto: identifica un intento lógico del usuario. Si reintentas con la misma llave, el servidor te devuelve el resultado de la primera ejecución en vez de ejecutar de nuevo.
Endpoints que requieren el encabezado
Estos endpoints de mutación del módulo de compras requieren Idempotency-Key:
POST /api/v1/purchases- crear orden de compra.POST /api/v1/purchases/{id}/send- enviar la orden al proveedor.POST /api/v1/purchases/{id}/cancel- cancelar la orden.POST /api/v1/purchases/{id}/receive- recibir mercancía.POST /api/v1/purchases/{purchaseId}/receipts/{receiptId}/emit-e41- emitir el comprobante de la recepción.
Período de gracia para integraciones existentes: si una integración aún no envía el encabezado, su próxima llamada a estos endpoints fallará con
400 IDEMPOTENCY_KEY_REQUIRED. Coordina con tu integrador para agregarlo antes de la próxima versión mayor.
Cómo generar la llave
- Formato: una cadena URL-safe de 16 a 128 caracteres usando el alfabeto
[A-Za-z0-9_-]. - Recomendado: un UUID v4 (
crypto.randomUUID(), 36 caracteres), un UUID v7 (ordenable), un ULID (26 caracteres) o un token base64url aleatorio. Todos son válidos. - Una llave por intento lógico. Genera una llave nueva por cada operación distinta. Reúsala SÓLO cuando reintentas exactamente la misma operación (por ejemplo, tras un timeout).
- No uses valores predecibles ni cortos: menos de 16 caracteres del alfabeto URL-safe deja muy poca entropía y abre riesgo de colisión accidental entre operaciones concurrentes.
Las respuestas posibles
| Situación | Respuesta |
|---|---|
| Primera vez con esa llave | El servidor ejecuta la operación y devuelve su resultado normal. |
| Reintento con la misma llave y el mismo cuerpo | El servidor devuelve la respuesta original (mismo código y cuerpo) sin ejecutar de nuevo. Esta es la garantía de "una sola ejecución". |
| Misma llave pero cuerpo diferente | 422 IDEMPOTENCY_KEY_BODY_MISMATCH - estás reciclando la llave para otra operación. Genera una llave nueva. |
| Dos reintentos concurrentes (misma llave, mismo cuerpo) | Uno ejecuta; los demás esperan hasta 30 s a que termine y luego reciben la misma respuesta. Si el primero se cuelga más de 30 s, los demás reciben 409 IDEMPOTENCY_KEY_TIMED_OUT y pueden reintentar después. |
Requisito crítico: el cuerpo debe ser byte-idéntico
El servidor calcula una huella del cuerpo JSON crudo y la compara contra la del primer envío. Por eso, en un reintento, el cuerpo debe serializarse byte por byte igual al original:
- Mismo orden de propiedades (o usa una serialización canónica).
- Mismo espaciado.
- Mismas representaciones numéricas -
5y"5"producen huellas distintas aunque el servidor luego los interprete igual.
Si no puedes garantizar bytes idénticos entre reintentos, entonces no es un reintento real: genera una llave nueva (es una operación nueva).
Códigos de error
| Código | Significado | Qué hacer |
|---|---|---|
IDEMPOTENCY_KEY_REQUIRED (400) | Falta el encabezado en un endpoint que lo exige. | Agrega Idempotency-Key. |
IDEMPOTENCY_KEY_INVALID_FORMAT (400) | El valor está fuera del formato (16 - 128 caracteres URL-safe). | Genera un valor válido (UUID/ULID). |
IDEMPOTENCY_KEY_BODY_MISMATCH (422) | Reusaste la llave con un cuerpo distinto. | Usa una llave nueva por operación nueva. |
IDEMPOTENCY_KEY_TIMED_OUT (409) | Otro reintento concurrente tardó demasiado. | Reintenta después; el sistema libera el bloqueo automáticamente. |
IDEMPOTENCY_REQUIRES_TENANT (400) | La petición no resolvió un tenant válido. | Revisa tus credenciales/token. |
Ventana de retención
La llave queda asociada a su resultado durante 24 horas desde el primer uso. Pasado ese tiempo se libera y podría reutilizarse - pero lo recomendado es generar siempre una llave nueva por operación.
Ejemplo con curl
curl -X POST https://api.tu-dominio.com/api/v1/purchases \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 1f2c4a9e-7b3d-4e21-9a6c-8d5f0b2e1c34" \
-d '{"supplierId":"sup_123","items":[{"productId":"prd_9","orderedQuantity":10,"unitCost":11000}]}'Si la llamada falla por timeout, repite exactamente la misma petición (mismo Idempotency-Key y mismo cuerpo): obtendrás el resultado original sin crear una segunda orden.
Ejemplo con fetch (JavaScript)
const idempotencyKey = crypto.randomUUID();
const body = JSON.stringify({
supplierId: 'sup_123',
items: [{ productId: 'prd_9', orderedQuantity: 10, unitCost: 11000 }],
});
async function createPurchase() {
return fetch('https://api.tu-dominio.com/api/v1/purchases', {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
'Idempotency-Key': idempotencyKey, // reusar el MISMO valor y el MISMO body al reintentar
},
body, // reusar la MISMA cadena exacta
});
}Relacionados
- Configurar un webhook
- Confiabilidad y reintentos
- Compras - las operaciones que cubren estos endpoints.
Probar un webhook
Cómo usar el botón de prueba de Mosce ERP para verificar la conectividad de un endpoint, interpretar el resultado y revisar el historial de entregas.
Exportar categorías
Cómo descargar tu catálogo de categorías de productos como un archivo CSV - con filtro por fecha, alcance configurable y respeto del plan.